feat: 修复困难帮扶申请表单校验及页面加载闪烁,调整办公室主任拒绝、退回审批时不校验级别和帮扶金额。

新增特殊慰问模块,支持分工会主席统一为多人申请、Excel 导入及人员校验;完善主从表存储、表单校验、审批展示和导出,移除分工会主席审批节点,使用独立流程节点标识。
This commit is contained in:
2026-10-30 16:30:48 +08:00
parent 29260423d5
commit 16cae3ed2b
42 changed files with 6754 additions and 12 deletions
@@ -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("当前流程不支持获取受助人所在单位分管校领导。");
}
@@ -55,8 +55,12 @@ public class DifficultHelpApprovalController {
if (StrUtil.isBlank(args.getStr("tf_opinion"))) throw new BaseException("请填写审批意见");
String level = args.getStr("level");
Double money = amountApproval ? args.getDouble("money") : null;
if (amountApproval) {
Integer submitType = args.getInt(FlowConst.SUBMIT_TYPE);
boolean skipAmount = "36ebafda-44c2-4bd3-bd7f-10839a575a0d".equals(task.getTaskName())
&& (ProcessSubmitTypeEnum.REJECT.getCode().equals(submitType)
|| ProcessSubmitTypeEnum.ROLLBACK_TO_OPERATOR.getCode().equals(submitType));
Double money = amountApproval && !skipAmount ? args.getDouble("money") : null;
if (amountApproval && !skipAmount) {
if (StrUtil.isBlank(level) || money == null || !Double.isFinite(money) || money < 0) {
throw new BaseException("请填写完整的级别和帮扶金额");
}
@@ -0,0 +1,251 @@
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.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;
@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.saveApplication(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 {
try (java.io.InputStream input = getClass().getResourceAsStream("/templates/special_condolence/special_condolence_import.xlsx")) {
if (input == null) throw new com.budwk.app.base.exception.BaseException("导入模板不存在");
com.budwk.app.base.utils.CommonDownloadUtil.download("特殊慰问导入模板.xlsx", 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;
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("第" + (r + 1) + "行不支持公式,请填写实际值");
if ((c == 2 || c == 5) && cell != null && cell.getCellType() == org.apache.poi.ss.usermodel.CellType.NUMERIC) throw new com.budwk.app.base.exception.BaseException("第" + (r + 1) + "行工号、身份证和收款账户请使用文本格式,避免数字失真");
row.set(fields[c], value);
empty = empty && value.isEmpty();
}
if (empty) continue;
for (int c = 0; c < fields.length; c++) {
if (c != 1 && row.getStr(fields[c]).isEmpty()) throw new com.budwk.app.base.exception.BaseException("第" + (r + 1) + "行:请填写" + headers[c]);
}
if (!com.budwk.app.zhgh.staffbenefit.specialcondolence.service.SpecialCondolenceRecipients.validIdCard(row.getStr("idCard"))) throw new com.budwk.app.base.exception.BaseException("第" + (r + 1) + "行身份证号码格式不正确");
if (!row.getStr("bankCardNumber").matches("[0-9]{6,30}")) throw new com.budwk.app.base.exception.BaseException("第" + (r + 1) + "行收款账户格式不正确");
com.budwk.app.zhgh.staffbenefit.specialcondolence.service.SpecialCondolenceRecipients.resolveUser(row, dao, "第" + (r + 1) + "行:");
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());
}
}
@@ -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();
}
}
@@ -0,0 +1,161 @@
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.*,
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 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);
}
}
@@ -0,0 +1,296 @@
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.*,
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 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;
}
@@ -0,0 +1,177 @@
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.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.sys.param.SysMsgSummaryPageForm;
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 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.*,
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.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 (!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");
} 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.*,
type.name as typeName
from
special_condolence con
left join wf_process_instance ins on ins.businessNo = con.id
left join special_condolence_type type on type.id = con.type
$condition
""");
Cnd cnd = Cnd.NEW();
cnd.and(ProcessInstance::getState, "=", ProcessTaskStateEnum.FINISHED.getCode());
cnd.andEX("con.type", "=", type);
cnd.andEX("YEAR(info.createTime)", "=", year);
cnd.andEX("con.applyUnionId", "=", unionId);
cnd.andEX("con.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 (!AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_CHAIRMAN.name())) {
cnd.and("con.applyUnionId", "=", SecurityUtil.getUnionId());
}
sql.setCondition(cnd);
List<NutMap> list = specialcondolenceService.listMap(sql);
List<ExcelExportEntity> exportEntities = new ArrayList<>();
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);
}
}
@@ -0,0 +1,133 @@
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.*,
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 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);
}
}
@@ -0,0 +1,137 @@
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.*,
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 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);
}
}
@@ -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);
}
}
@@ -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));
}
}
@@ -0,0 +1,304 @@
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("申请人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;
}
@@ -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;
}
@@ -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;
}
@@ -0,0 +1,117 @@
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) {
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());
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 {
org.nutz.dao.Cnd identity = org.nutz.dao.Cnd.where("unionId", "=", unionId)
.and("idCard", "=", row.getStr("idCard")).and("username", "=", row.getStr("helpUserName"));
java.util.List<com.budwk.app.sys.views.View_user> dispatched = dao.query(com.budwk.app.sys.views.View_user.class,
identity.and("personType", "=", "劳务派遣人员"));
if (dispatched.size() > 1) throw new BaseException(prefix + "劳务派遣人员身份重复,请核对档案");
if (dispatched.size() == 1) {
user = dispatched.get(0);
} else {
if (cn.hutool.core.util.StrUtil.isBlank(row.getStr("helpLoginName"))) throw new BaseException(prefix + "普通人员必须填写工号");
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("unionId", "=", unionId).and("loginname", "=", row.getStr("helpLoginName"))
.and("username", "=", row.getStr("helpUserName")).and("idCard", "=", row.getStr("idCard")));
if (users.size() != 1) throw new BaseException(prefix + "工号、姓名、身份证不匹配或不属于申请人本工会");
user = users.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")) || !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));
}
}
@@ -0,0 +1,17 @@
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);
SpecialCondolence loadApplication(String id);
void deleteApplication(String id);
}
@@ -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> {
}
@@ -0,0 +1,87 @@
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.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());
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(new BigDecimal(row.getStr("money")));
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);
}
}
@@ -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);
}
}
@@ -0,0 +1,223 @@
-- 全量初始化:完整定义特殊慰问申请主表、被申请人明细表和慰问类型表。
-- 表已存在时不执行本文件中的建表语句;本文件不复制数据、不配置角色授权和工作流。
CREATE TABLE `special_condolence` (
`id` varchar(32) NOT NULL COMMENT 'ID',
`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', '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', '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', '9c37242f50c5fb4a9c1b2b9b9dd0e2fd');
-- 特殊慰问 h5
INSERT INTO sys_role_menu (roleId, menuId)
VALUES
('b3854469e6f44901b22bb791cb538431', '2145812145ee6548552504b4f5a55ff7'),
('b3854469e6f44901b22bb791cb538431', '739f64c71fe776dc3520ebb2977c2d68');
COMMIT;
@@ -1,8 +1,9 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app">
<el-card shadow="never" v-if="isShow">
<div id="app" v-cloak>
<el-card shadow="never" v-if="pageLoading" v-loading="pageLoading" element-loading-text="正在加载申请信息" style="min-height: 300px"></el-card>
<el-card shadow="never" v-else-if="isShow">
<snaker-start slot="header" label="困难帮扶申请" define_key="KNBF"></snaker-start>
<el-form :model="formData" ref="formRef" :rules="formRules" label-width="0" label-suffix=":" class="flow-task-form">
<el-descriptions :column="3" border>
@@ -318,6 +319,7 @@ layout("/layouts/platform.html"){
bizId: GetQueryString("bizId"),
taskId: GetQueryString("taskId"),
isShow: false,
pageLoading: true,
time: '',
blockMessage: '',
formData: {
@@ -453,10 +455,12 @@ layout("/layouts/platform.html"){
})
},
findOne(id) {
this.$axios.post('/platform/difficultHelp/mine/findOne', { id }).then(res => {
return this.$axios.post('/platform/difficultHelp/mine/findOne', { id }).then(res => {
if (res.code === 0) {
this.formData = res.data
this.queryRecipients(res.data.loginName)
} else {
throw new Error(res.msg || '申请信息加载失败,请刷新页面重试')
}
})
},
@@ -617,7 +621,7 @@ layout("/layouts/platform.html"){
return { status: 200, msg: "卡号有效。" };
},
async getIsCanPlay() {
this.$axios.post('/platform/difficultHelp/apply/getIsCanPlay').then(res => {
return this.$axios.post('/platform/difficultHelp/apply/getIsCanPlay').then(res => {
if (res.code === 0) {
// 新增申请进入页面时,优先校验当前登录人本年度是否已经申请过,已申请则直接拦截新增。
if (!this.bizId && res.data.hasCurrentYearApply && !this.$auth.hasRoleOr('BRANCH_UNION_CHAIRMAN')) {
@@ -631,16 +635,25 @@ layout("/layouts/platform.html"){
if (res.data.applyCount !== null && res.data.applyCount !== undefined && res.data.applyCount !== 0) {
this.$set(this.formData, 'applyCount', res.data.applyCount)
}
} else {
throw new Error(res.msg || '申请资格加载失败,请刷新页面重试')
}
})
}
},
async created() {
await this.getIsCanPlay();
if (this.bizId) {
this.findOne(this.bizId)
} else {
this.modeChange("1")
try {
await this.getIsCanPlay();
if (this.bizId) {
await this.findOne(this.bizId)
} else {
this.modeChange("1")
}
} catch (error) {
this.isShow = false
this.blockMessage = error.message || '申请信息加载失败,请刷新页面重试'
} finally {
this.pageLoading = false
}
}
})
@@ -305,7 +305,16 @@ layout("/layouts/platform.html"){
async handleTaskAction(val) {
try {
const valid = await this.$refs.formRef.validate()
let valid
if (val === 2 || val === 6) {
this.$refs.formRef.clearValidate(['level', 'money'])
const results = await Promise.all(['tf_opinion', 'tf_sign'].map(prop => new Promise(resolve => {
this.$refs.formRef.validateField(prop, error => resolve(!error))
})))
valid = results.every(result => result)
} else {
valid = await this.$refs.formRef.validate()
}
if (!valid) return
} catch (error) {
return
@@ -0,0 +1,301 @@
<!--#
layout("/layouts/platform.html"){
#-->
<style nonce="${cspNonce!}">
<!--#include('recipients.css'){}#-->
</style>
<div id="app" class="special-condolence-apply" v-cloak>
<el-card shadow="never">
<snaker-start slot="header" label="特殊慰问" define_key="TSWW"></snaker-start>
<el-form size="small" :model="formData" ref="formRef" :rules="formRules" label-width="0" label-suffix=":"
class="flow-task-form">
<div class="special-condolence-section-title">申请人信息</div>
<el-descriptions :column="2" border size="small">
<el-descriptions-item label="申请人姓名">{{formData.applyUserName}}</el-descriptions-item>
<el-descriptions-item label="申请人工号">{{formData.applyLoginName}}</el-descriptions-item>
<el-descriptions-item label="申请人单位">{{formData.applyUnitName}}</el-descriptions-item>
<el-descriptions-item label="申请人工会">{{formData.applyUnionName}}</el-descriptions-item>
</el-descriptions>
<!--#include('recipients.html'){}#-->
<div class="special-condolence-section-title">申请说明与签字</div>
<el-descriptions :column="3" border size="small">
<el-descriptions-item label="申请事由" :span="3">
<el-form-item prop="remark" label="申请事由">
<el-input maxlength="500" v-model="formData.remark" placeholder="请填写申请事由"
type="textarea" :rows="3" resize="none"></el-input>
</el-form-item>
</el-descriptions-item>
<el-descriptions-item :span="3" v-if="chooseType.isUploadFile === true">
<template slot="label">
附件
<el-tooltip class="item" effect="dark" :content="'上传附件说明:' + chooseType.uploadFileDesc"
placement="top-start"
v-if="chooseType.isUploadFile && chooseType.uploadFileDesc">
<i class="el-icon-question"></i>
</el-tooltip>
</template>
<el-form-item prop="files" label="附件">
<file-upload
:value.sync="formData.files"
:upload_number="5"
upload_result_category="array"
complete_result
upload_mode="drag"
></file-upload>
</el-form-item>
</el-descriptions-item>
<el-descriptions-item label="签字" :span="3">
<el-form-item prop="signature" label="签字">
<pc-signature v-model="formData.signature"></pc-signature>
</el-form-item>
</el-descriptions-item>
</el-descriptions>
</el-form>
<el-row type="flex" justify="end" class="special-condolence-actions">
<el-button type="primary" plain @click="onSave">保存申请</el-button>
<el-button type="primary" @click="onSubmit" v-if="!taskId">提交申请</el-button>
<el-button type="primary" @click="onFinishTask" v-else>提交申请</el-button>
</el-row>
</el-card>
</div>
<script nonce="${cspNonce!}">
<!--#include('recipients.js'){}#-->
new Vue({
el: "#app",
store,
mixins: [specialCondolenceRecipients],
components: {
ElDatePicker: {
extends: ELEMENT.DatePicker,
mounted() {
// 手动输入触发 change 前初始化日期面板,避免 picker 尚未创建。
if (!this.picker) this.mountPicker()
}
}
},
data() {
return {
bizId: GetQueryString("bizId"),
taskId: GetQueryString("taskId"),
formData: { recipients: [] },
formRules: {
recipients: [{ validator: (rule, value, callback) => callback(value && value.length ? undefined : new Error("请添加慰问对象")), trigger: "change" }],
helpUserId: [{ required: true, message: "必填", trigger: ["change", "blur"] }],
payUserId: [{ required: true, message: "必填", trigger: ["change", "blur"] }],
type: [{ required: true, message: "必填", trigger: ["change", "blur"] }],
way: [{ required: true, message: "必填", trigger: ["change", "blur"] }],
money: [
{ required: true, message: "请填写慰问金额", trigger: ["change", "blur"] },
{ validator: (rule, value, callback) => callback(this.validateMoney(value) ? undefined : new Error("慰问金额须大于0且不超过10000元,最多两位小数")), trigger: ["change", "blur"] }
],
occurTime: [{ required: true, message: "必填", trigger: ["change", "blur"] }],
child: [{ required: true, message: "必填", trigger: ["change", "blur"] }],
hospital: [{ required: true, message: "必填", trigger: ["change", "blur"] }],
hospitalHouseNum: [{ required: true, message: "必填", trigger: ["change", "blur"] }],
hospitalHouseBedNum: [{ required: true, message: "必填", trigger: ["change", "blur"] }],
hospitalBy: [{ required: true, message: "必填", trigger: ["change", "blur"] }],
deadImmediateFamily: [{ required: true, message: "必填", trigger: ["change", "blur"] }],
remark: [{ required: true, message: "必填", trigger: ["change", "blur"] }],
files: [{ required: true, type: "array", min: 1, max: 5, message: "请上传1至5个附件", trigger: ["change", "blur"] }],
signature: [{ required: true, message: "必填", trigger: ["change", "blur"] }],
handlerUserId: [{ required: true, message: "必填", trigger: ["change", "blur"] }],
// certifierUserId: [{ required: true, message: "必填", trigger: ["change", "blur"] }],
bankCardNumber: [
{ required: true, message: "请填写银行卡号或对公账户号", trigger: ["change", "blur"] },
{ pattern: /^[0-9]{6,30}$/, message: "收款账户须为6至30位数字(银行卡号或对公账户号)", trigger: ["change", "blur"] }
],
bankUserName: [{ required: true, message: "必填", trigger: ["change", "blur"] }],
bankOfDeposit: [{ required: true, message: "必填", trigger: ["change", "blur"] }],
thisYearHospitalizationNum: [
{ required: true, message: "必填", trigger: ["change", "blur"] },
{ type: "integer", min: 0, max: 10000, message: "住院次数须为0至10000的整数", trigger: ["change", "blur"] }
],
leaveHospitalTime: [{ required: true, message: "必填", trigger: ["change", "blur"] }],
hospitalizationTime: [{ required: true, message: "必填", trigger: ["change", "blur"] }],
},
chooseType: {},
payUserOptions: [],
helpUserOptions: [],
typeOptions: [],
handlerUserOptions: [],
certifierUserOptions: []
}
},
methods: {
validateMoney(value) {
return /^(0|[1-9][0-9]*)(\.[0-9]{1,2})?$/.test(String(value)) && Number(value) > 0 && Number(value) <= 10000
},
createRemoteMethod(options) {
return (keyword) => {
this.selectQueryUser(keyword, options)
}
},
selectQueryUser(keyword, options) {
options.length = 0
this.$axios.post("/platform/special_condolence/apply/listUser", { keyword: keyword }).then((res) => {
if (res.code === 0) {
options.push(...res.data)
}
})
},
helpUserChange(val) {
const user = this.helpUserOptions.find(o => o.id === val)
if (user) {
const { userName, loginName, unitId, unitName, unionId, unionName } = user
this.$set(this.formData, "helpUserName", userName)
this.$set(this.formData, "helpLoginName", loginName)
this.$set(this.formData, "helpUnitId", unitId)
this.$set(this.formData, "helpUnitName", unitName)
this.$set(this.formData, "helpUnionId", unionId)
this.$set(this.formData, "helpUnionName", unionName)
}
},
handlerUserChange(val) {
const user = this.handlerUserOptions.find(o => o.id === val)
if (user) {
const { userName, loginName, unitId, unitName, unionId, unionName } = user
this.$set(this.formData, "handlerUserName", userName)
this.$set(this.formData, "handlerLoginName", loginName)
this.$set(this.formData, "handlerUnitId", unitId)
this.$set(this.formData, "handlerUnitName", unitName)
this.$set(this.formData, "handlerUnionId", unionId)
this.$set(this.formData, "handlerUnionName", unionName)
}
},
certifierUserChange(val) {
const user = this.certifierUserOptions.find(o => o.id === val)
if (user) {
const { userName, loginName, unitId, unitName, unionId, unionName } = user
this.$set(this.formData, "certifierUserName", userName)
this.$set(this.formData, "certifierLoginName", loginName)
this.$set(this.formData, "certifierUnitId", unitId)
this.$set(this.formData, "certifierUnitName", unitName)
this.$set(this.formData, "certifierUnionId", unionId)
this.$set(this.formData, "certifierUnionName", unionName)
}
},
payUserChange(val) {
const user = this.payUserOptions.find(o => o.id === val)
if (user) {
const { userName, loginName } = user
this.$set(this.formData, "payUserName", userName)
this.$set(this.formData, "payLoginName", loginName)
}
},
typeChange(id) {
this.chooseType = this.typeOptions.find(o => o.id === id)
if (this.chooseType) {
this.$set(this.formData, "perMoney", this.chooseType.money)
this.$set(this.formData, "way", this.chooseType.way)
this.$set(this.formData, "typeCode", this.chooseType.code)
}
},
onSave() {
this.$refs.formRef.validate((valid) => {
if (!valid) return
this.$confirm("您确定保存吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
this.$axios.post("/platform/special_condolence/apply/save", { data: JSON.stringify(this.formData) }).then(res => {
if (res.code === 0) {
this.$message.success(res.msg)
commonUtil.pjaxPush("/platform/special_condolence/mine")
}
})
})
})
},
onSubmit() {
this.$refs.formRef.validate((valid) => {
if (valid) {
this.$confirm("您确定要提交吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
this.$axios.post("/platform/special_condolence/apply/submit", {
data: JSON.stringify(this.formData)
}).then(res => {
if (res.code === 0) {
this.$message.success(res.msg)
commonUtil.pjaxPush("/platform/special_condolence/mine")
}
})
})
}
})
},
onFinishTask() {
this.$refs.formRef.validate((valid) => {
if (valid) {
this.$confirm("您确定要提交吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
this.$axios.post("/platform/special_condolence/apply/submitAgain", {
data: JSON.stringify(this.formData),
taskId: GetQueryString("taskId")
}).then(res => {
if (res.code === 0) {
this.$message.success(res.msg)
commonUtil.pjaxPush("/platform/special_condolence/mine")
}
})
})
}
})
},
init() {
if (this.bizId) {
this.$axios.post("/platform/special_condolence/mine/info", { id: this.bizId }).then((res) => {
if (res.code === 0) {
this.formData = res.data
this.initRecipients()
this.selectQueryUser(this.formData.helpLoginName, this.helpUserOptions)
this.selectQueryUser(this.formData.certifierLoginName, this.certifierUserOptions)
this.selectQueryUser(this.formData.handlerLoginName, this.handlerUserOptions)
this.chooseType = this.typeOptions.find(o => o.id === this.formData.type)
}
})
} else {
const { username, loginname, id, unit, union, mobile } = this.$store.state.user
this.formData = {
recipients: [this.newRecipient()],
perMoney: undefined,
occurTime: this.$moment().format('YYYY-MM-DD'),
applyUserName: username,
applyLoginName: loginname,
applyUserId: id,
applyUnitId: unit?.id,
applyUnitName: unit?.name,
applyUnionId: union?.id,
applyUnionName: union?.name
}
}
},
querySpecialCondolenceType() {
this.$axios.post("/platform/special_condolence/type/querySpecialCondolenceType")
.then((resp) => {
this.typeOptions = resp.data
})
}
},
created() {
this.querySpecialCondolenceType()
this.init()
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,63 @@
.special-condolence-apply .el-card__body { padding: 24px; }
.special-condolence-apply .special-condolence-section-title { display: flex; align-items: center; gap: 10px; margin: 24px 0 14px; color: #303133; font-size: 15px; font-weight: 600; line-height: 24px; }
.special-condolence-apply .el-descriptions__table { table-layout: fixed; }
.special-condolence-apply .flow-task-form > .special-condolence-section-title:first-child { margin-top: 0; }
.special-condolence-apply .el-descriptions { font-size: 14px; }
.special-condolence-apply .el-descriptions .el-descriptions-item__cell { height: 50px; padding: 8px 12px; box-sizing: border-box; font-size: 14px; line-height: 32px; vertical-align: middle; }
.special-condolence-apply .el-descriptions-item__label.is-bordered-label { width: 130px; min-width: 130px; max-width: 130px; font-weight: 400; }
.special-condolence-apply .el-descriptions-item__content { font-weight: 400; }
.special-condolence-apply .el-descriptions .el-form-item__content { line-height: 32px; }
.special-condolence-apply .el-descriptions .el-input__inner { height: 32px; line-height: 32px; font-size: 14px; }
.special-condolence-apply .el-descriptions .el-input__icon { line-height: 32px; }
.special-condolence-apply .el-descriptions .el-textarea__inner { font-size: 14px; line-height: 22px; }
.special-condolence-apply .el-descriptions-item__label { width: 130px; color: #606266; background: #f7f9fc; }
.special-condolence-apply .special-condolence-recipients { margin: 28px 0; border: 1px solid #e4e7ed; border-radius: 6px; overflow: hidden; }
.special-condolence-apply .special-condolence-toolbar { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 14px; padding: 18px 20px; }
.special-condolence-apply .special-condolence-toolbar .special-condolence-section-title { margin: 0 0 4px; }
.special-condolence-hint { color: #606266; font-size: 12px; line-height: 20px; }
.special-condolence-apply .special-condolence-toolbar-buttons { display: flex; flex-wrap: wrap; gap: 8px; }
.special-condolence-apply .special-condolence-toolbar-buttons .el-button { margin: 0; }
.special-condolence-apply .special-condolence-table-item { margin: 0; }
.special-condolence-apply .special-condolence-table-item > .el-form-item__content { line-height: normal; }
.special-condolence-apply .special-condolence-table-item .el-form-item__error { position: static; padding: 8px 20px; }
.special-condolence-apply .special-condolence-table-item .el-table th { background: #f5f7fa; color: #606266; font-weight: 600; }
.special-condolence-apply .special-condolence-table-item .el-table .cell { padding: 0 8px; }
.special-condolence-apply .special-condolence-table-item .el-select,
.special-condolence-apply .special-condolence-table-item .el-input-number,
.special-condolence-apply .special-condolence-table-item .el-date-editor { width: 100%; }
.special-condolence-apply .special-condolence-table-item .el-input-number .el-input__inner { text-align: right; }
.special-condolence-apply .special-condolence-delete { color: #d64040; }
.special-condolence-apply .special-condolence-table-footer { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 10px; padding: 10px 20px; background: #fafbfd; }
.special-condolence-apply .special-condolence-total { display: flex; align-items: baseline; gap: 6px; color: #606266; font-size: 13px; font-variant-numeric: tabular-nums; }
.special-condolence-apply .special-condolence-total > span { margin-left: 18px; }
.special-condolence-apply .special-condolence-total-amount { color: #303133; font-size: 20px; }
.special-condolence-apply .special-condolence-actions { gap: 12px; margin-top: 24px; padding-top: 20px; border-top: 1px solid #ebeef5; }
.special-condolence-apply .special-condolence-actions .el-button { margin: 0; min-width: 108px; }
.special-condolence-dialog { max-width: calc(100vw - 32px); border-radius: 8px; }
.special-condolence-dialog .el-dialog__header { padding: 22px 24px 16px; border-bottom: 1px solid #ebeef5; }
.special-condolence-dialog .el-dialog__body { padding: 24px; }
.special-condolence-dialog .el-dialog__footer { padding: 16px 24px; border-top: 1px solid #ebeef5; }
.special-condolence-dialog .special-condolence-template { display: flex; align-items: center; flex-wrap: wrap; gap: 12px; padding: 16px; border: 1px solid #dce6f3; border-radius: 6px; background: #f6f9fe; }
.special-condolence-dialog .special-condolence-template > i { color: #409eff; font-size: 28px; }
.special-condolence-dialog .special-condolence-template > div { flex: 1; min-width: 180px; }
.special-condolence-dialog .special-condolence-template p { margin: 5px 0 0; color: #606266; font-size: 12px; line-height: 18px; }
.special-condolence-dialog .special-condolence-upload-title { margin: 22px 0 12px; font-weight: 600; }
.special-condolence-dialog .special-condolence-upload .el-upload,
.special-condolence-dialog .special-condolence-upload .el-upload-dragger { width: 100%; }
.special-condolence-dialog .special-condolence-upload .el-upload-dragger { height: auto; min-height: 150px; padding: 22px 12px; }
.special-condolence-dialog .special-condolence-upload .el-icon-upload,
.special-condolence-dialog .special-condolence-upload .el-icon-loading { display: block; margin: 0 0 16px; color: #909399; font-size: 44px; line-height: 48px; }
.special-condolence-dialog .el-upload__tip { margin-bottom: 16px; line-height: 20px; }
.special-condolence-dialog .special-condolence-batch-note { margin-bottom: 24px; padding: 12px 16px; background: #f5f7fa; border-radius: 4px; line-height: 22px; }
.special-condolence-dialog .special-condolence-batch-form .el-select,
.special-condolence-dialog .special-condolence-batch-form .el-input-number,
.special-condolence-dialog .special-condolence-batch-form .el-date-editor { width: 100%; }
@media (max-width: 768px) {
.special-condolence-apply .el-card__body { padding: 16px; }
.special-condolence-apply .special-condolence-toolbar { padding: 14px; }
.special-condolence-apply .special-condolence-total > span { margin-left: 8px; }
}
.special-condolence-apply .special-condolence-table-item .el-table .el-form-item { margin: 8px 0 20px; }
.special-condolence-apply .special-condolence-table-item .el-table .el-form-item__error { position: absolute; padding: 4px 0 0; font-size: 11px; line-height: 14px; white-space: normal; }
@@ -0,0 +1,63 @@
<div class="special-condolence-section-title">统一慰问配置</div>
<el-descriptions :column="3" border size="small">
<el-descriptions-item label="慰问类型">
<el-form-item label="慰问类型" prop="type">
<el-select v-model="formData.type" filterable @change="typeChange" style="width:100%" placeholder="请选择慰问类型">
<el-option v-for="t in typeOptions" :key="t.id" :value="t.id" :label="t.name"></el-option>
</el-select>
</el-form-item>
</el-descriptions-item>
<el-descriptions-item label="慰问金额">
<el-form-item label="慰问金额" prop="perMoney" :rules="recipientRules.money">
<el-input-number v-model="formData.perMoney" :precision="2" :min="0" :max="10000" :controls="false" style="width:100%" placeholder="每人慰问金额(元)"></el-input-number>
</el-form-item>
</el-descriptions-item>
<el-descriptions-item label="慰问时间">
<el-form-item label="慰问时间" prop="occurTime" :rules="recipientRules.occurTime">
<el-date-picker v-model="formData.occurTime" type="date" value-format="yyyy-MM-dd" style="width:100%" placeholder="请选择慰问时间"></el-date-picker>
</el-form-item>
</el-descriptions-item>
</el-descriptions>
<section class="special-condolence-recipients">
<div class="special-condolence-toolbar">
<div>
<div class="special-condolence-section-title">慰问对象明细 <el-tag size="small" effect="plain">{{ recipientCount }} 人</el-tag></div>
<div class="special-condolence-hint">一行填写一位慰问对象,所有人员随本申请统一审批。</div>
</div>
<div class="special-condolence-toolbar-buttons">
<el-button size="small" icon="el-icon-upload2" @click="importVisible = true">Excel 导入</el-button>
<el-button size="small" type="primary" icon="el-icon-plus" @click="formData.recipients.push(newRecipient())">新增慰问对象</el-button>
</div>
</div>
<el-form-item prop="recipients" label-width="0" class="special-condolence-table-item">
<el-table :data="formData.recipients" border stripe size="small" max-height="520" empty-text="暂无慰问对象,点击新增或从 Excel 导入">
<el-table-column type="index" label="序号" width="55" align="center" fixed="left"></el-table-column>
<el-table-column label="慰问对象" width="200" fixed="left"><template slot-scope="s"><el-form-item :prop="'recipients.' + s.$index + '.helpUserId'" :rules="recipientRules.helpUserId"><user-select size="small" :ref="'recipientUser' + s.$index" v-model="s.row.helpUserId" api="/platform/special_condolence/apply/listUser" api_input_key_name="keyword" :option_list="recipientOptions(s.row)" :option_label_func="item => item.userName + '(' + (item.loginName || '') + ')'" @hook:mounted="seedRecipientOptions(s.row, s.$index)" @change="id => recipientUserChange(s.row, s.$index, id)"></user-select></el-form-item></template></el-table-column>
<el-table-column label="工号" min-width="150"><template slot-scope="s"><el-form-item :prop="'recipients.' + s.$index + '.helpLoginName'" :rules="[{ validator: (rule, value, cb) => cb(s.row.personType === '劳务派遣人员' || value ? undefined : new Error('普通人员工号不能为空')), trigger: 'change' }]"><el-input size="small" v-model="s.row.helpLoginName" readonly placeholder="选人后带出,劳务派遣可空"></el-input></el-form-item></template></el-table-column>
<el-table-column label="身份证号码" min-width="205"><template slot-scope="s"><el-form-item :prop="'recipients.' + s.$index + '.idCard'" :rules="recipientRules.idCard"><el-input size="small" v-model="s.row.idCard" maxlength="18" placeholder="请输入18位身份证号"></el-input></el-form-item></template></el-table-column>
<el-table-column label="开户行" min-width="190"><template slot-scope="s"><el-form-item :prop="'recipients.' + s.$index + '.bankOfDeposit'" :rules="recipientRules.bankOfDeposit"><el-input size="small" v-model="s.row.bankOfDeposit" maxlength="100" placeholder="请输入开户银行及支行"></el-input></el-form-item></template></el-table-column>
<el-table-column label="户名" min-width="140"><template slot-scope="s"><el-form-item :prop="'recipients.' + s.$index + '.bankUserName'" :rules="recipientRules.bankUserName"><el-input size="small" v-model="s.row.bankUserName" maxlength="100" placeholder="请输入账户户名"></el-input></el-form-item></template></el-table-column>
<el-table-column label="收款账户" min-width="215"><template slot-scope="s"><el-form-item :prop="'recipients.' + s.$index + '.bankCardNumber'" :rules="recipientRules.bankCardNumber"><el-input size="small" v-model="s.row.bankCardNumber" maxlength="30" placeholder="银行卡号或对公账户号"></el-input></el-form-item></template></el-table-column>
<el-table-column label="操作" fixed="right" width="75" align="center"><template slot-scope="s"><el-button size="small" type="text" class="special-condolence-delete" @click="formData.recipients.splice(s.$index, 1)">删除</el-button></template></el-table-column>
</el-table>
</el-form-item>
<div class="special-condolence-table-footer">
<el-button type="text" icon="el-icon-plus" @click="formData.recipients.push(newRecipient())">继续添加一行</el-button>
<div class="special-condolence-total">共 <strong>{{ recipientCount }}</strong> 人<span>慰问金额合计</span><strong class="special-condolence-total-amount">¥ {{ recipientTotal }}</strong></div>
</div>
</section>
<el-dialog title="Excel 导入慰问对象" :visible.sync="importVisible" width="560px" custom-class="special-condolence-dialog" :close-on-click-modal="!importLoading" :close-on-press-escape="!importLoading" :show-close="!importLoading" append-to-body>
<div class="special-condolence-template">
<i class="el-icon-document"></i>
<div><strong>第一步:下载并填写模板</strong><p>按模板表头填写,一行对应一位慰问对象。</p></div>
<el-button size="small" type="primary" plain icon="el-icon-download" @click="$downLoad('/platform/special_condolence/apply/importTemplate')">下载模板</el-button>
</div>
<div class="special-condolence-upload-title">第二步:上传填写好的文件</div>
<el-upload class="special-condolence-upload" drag action="" accept=".xlsx,.xls" :http-request="importRecipients" :show-file-list="false" :disabled="importLoading">
<i :class="importLoading ? 'el-icon-loading' : 'el-icon-upload'"></i>
<div class="el-upload__text">{{ importLoading ? '正在导入,请稍候…' : '将 Excel 文件拖到此处,或点击选择文件' }}</div>
<div slot="tip" class="el-upload__tip">支持 .xlsx、.xls 格式,单次最多 1000 行。导入的数据将追加到当前表格。</div>
</el-upload>
<el-alert title="工号、身份证号码和收款账户请保持文本格式。普通人员须填写正确工号;劳务派遣人员免校验工号,均须属于申请人本工会。" type="info" :closable="false" show-icon></el-alert>
<span slot="footer"><el-button :disabled="importLoading" @click="importVisible = false">关闭</el-button></span>
</el-dialog>
@@ -0,0 +1,88 @@
const specialCondolenceRecipients = {
data() {
return { recipientRules: this.createRecipientRules(), importVisible: false, importLoading: false }
},
computed: {
recipientCount() {
return (this.formData.recipients || []).length
},
recipientTotal() {
const money = Number(this.formData.perMoney)
const cents = (Number.isFinite(money) && money > 0 ? Math.round(money * 100) : 0) * this.recipientCount
return (cents / 100).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
}
},
watch: {
typeOptions() { this.chooseType = this.typeOptions.find(t => t.id === this.formData.type) || {} },
'formData.type'(value) { this.chooseType = this.typeOptions.find(t => t.id === value) || {} }
},
methods: {
createRecipientRules() {
const required = label => ({ required: true, whitespace: true, message: '请填写' + label, trigger: ['blur', 'change'] })
return {
helpUserId: [{ required: true, message: '请选择慰问对象', trigger: 'change' }],
type: [{ required: true, message: '请选择慰问类型', trigger: 'change' }],
idCard: [required('身份证号码'), { validator: (rule, value, cb) => cb(this.validRecipientIdCard(value) ? undefined : new Error('身份证号码不正确')), trigger: ['blur', 'change'] }],
bankOfDeposit: [required('开户行'), { max: 100, message: '开户行最多100字', trigger: 'blur' }],
bankUserName: [required('户名'), { max: 100, message: '户名最多100字', trigger: 'blur' }],
bankCardNumber: [required('收款账户'), { pattern: /^[0-9]{6,30}$/, message: '请输入6至30位数字账户', trigger: ['blur', 'change'] }],
money: [{ validator: (rule, value, cb) => cb(/^(0|[1-9][0-9]*)(\.[0-9]{1,2})?$/.test(String(value)) && Number(value) > 0 && Number(value) <= 10000 ? undefined : new Error('金额须大于0且不超过10000元,最多两位小数')), trigger: ['blur', 'change'] }],
occurTime: [{ validator: (rule, value, cb) => cb(this.$moment(value, 'YYYY-MM-DD', true).isValid() ? undefined : new Error('请选择有效慰问时间')), trigger: ['blur', 'change'] }]
}
},
validRecipientIdCard(value) {
const card = String(value || '').toUpperCase()
if (!/^[1-9][0-9]{16}[0-9X]$/.test(card) || !this.$moment(card.substring(6, 14), 'YYYYMMDD', true).isValid()) return false
const weights = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2]
return '10X98765432'[weights.reduce((sum, weight, index) => sum + weight * Number(card[index]), 0) % 11] === card[17]
},
recipientOptions(row) {
return row.helpUserId ? [{ id: row.helpUserId, userName: row.helpUserName, loginName: row.helpLoginName }] : []
},
seedRecipientOptions(row, index) {
const component = this.$refs['recipientUser' + index]
if (component && row.helpUserId) component.options = this.recipientOptions(row)
},
recipientUserChange(row, index, id) {
const component = this.$refs['recipientUser' + index]
const user = component && component.options.find(item => item.id === id)
this.$set(row, 'helpUserId', user ? user.id : '')
this.$set(row, 'helpUserName', user ? user.userName : '')
this.$set(row, 'helpLoginName', user ? user.loginName : '')
this.$set(row, 'personType', user ? user.personType : '')
this.$set(row, 'idCard', user ? user.idCard || '' : '')
this.$set(row, 'bankOfDeposit', user ? user.bankOfDeposit || '' : '')
this.$set(row, 'bankUserName', user ? user.bankUserName || user.userName : '')
this.$set(row, 'bankCardNumber', user ? user.bankCardNumber || '' : '')
this.$nextTick(() => this.$refs.formRef.validateField('recipients.' + index + '.helpUserId'))
},
newRecipient() {
return { helpUserId: '', helpLoginName: '', personType: '', helpUserName: '', idCard: '', bankOfDeposit: '', bankUserName: '', bankCardNumber: '' }
},
initRecipients() {
if (this.formData.perMoney == null && this.formData.recipients && this.formData.recipients.length) this.$set(this.formData, 'perMoney', Number(this.formData.recipients[0].money))
if (!this.formData.recipients || !this.formData.recipients.length) {
this.$set(this.formData, 'recipients', this.formData.helpUserName ? [Object.assign(this.newRecipient(), this.formData, { recipients: undefined })] : [this.newRecipient()])
}
},
async importRecipients(request) {
this.importLoading = true
try {
const data = new FormData()
data.append('file', request.file)
const result = await this.$axios.post('/platform/special_condolence/apply/importRecipients', data)
if (result.code === 0) {
const rows = this.formData.recipients.filter(r => r.helpUserName || r.type || r.idCard || r.bankCardNumber || r.bankUserName || r.bankOfDeposit || r.money)
this.$set(this.formData, 'recipients', rows.concat(result.data))
this.importVisible = false
this.$message.success('导入成功,共' + result.data.length + '人')
} else this.$message.error(result.msg || '导入失败')
} catch (error) {
this.$message.error('导入失败,请检查文件内容后重试')
} finally {
this.importLoading = false
}
}
}
}
@@ -0,0 +1,157 @@
const specialcondolenceInfo = {
template: /*language=HTML*/ `
<div>
<div class="process-title">
申请信息
<el-link type="primary" @click="openChart">点击查看流程图</el-link>
</div>
<el-descriptions :column="3" border>
<el-descriptions-item label="申请人姓名">{{ viewData.applyUserName }}</el-descriptions-item>
<el-descriptions-item label="申请人工号">{{ viewData.applyLoginName }}</el-descriptions-item>
<el-descriptions-item label="申请人单位">{{ viewData.applyUnitName }}</el-descriptions-item>
<el-descriptions-item label="申请人工会">{{ viewData.applyUnionName }}</el-descriptions-item>
<template v-if="viewData.recipients && viewData.recipients.length">
<el-descriptions-item label="慰问类型">{{ viewData.typeName }}</el-descriptions-item>
<el-descriptions-item label="每人慰问金额">{{ viewData.perMoney }} 元</el-descriptions-item>
<el-descriptions-item label="慰问时间">{{ viewData.occurTime }}</el-descriptions-item>
</template>
<template v-if="!viewData.recipients || !viewData.recipients.length">
<el-descriptions-item label="慰问对象">{{ viewData.helpUserName }}</el-descriptions-item>
<!-- <el-descriptions-item label="经办人">{{ viewData.handlerUserName }}</el-descriptions-item>-->
<el-descriptions-item label="证明人">{{ viewData.certifierUserName }}</el-descriptions-item>
<el-descriptions-item label="慰问类型">{{ viewData.typeName }}</el-descriptions-item>
<el-descriptions-item label="慰问金额">{{ viewData.money }}</el-descriptions-item>
<el-descriptions-item label="慰问时间">{{ viewData.occurTime }}</el-descriptions-item>
<el-descriptions-item label="收款账户">{{ viewData.bankCardNumber }}</el-descriptions-item>
<el-descriptions-item label="户名">{{ viewData.bankUserName }}</el-descriptions-item>
<el-descriptions-item label="开户行">{{ viewData.bankOfDeposit }}</el-descriptions-item>
<template v-if="['2'].includes(viewData.typeCode)">
<el-descriptions-item label="入院时间">{{ viewData.hospitalizationTime }}</el-descriptions-item>
<el-descriptions-item label="出院时间">{{ viewData.leaveHospitalTime }}</el-descriptions-item>
<el-descriptions-item label="当年第几次住院">{{ viewData.thisYearHospitalizationNum }}
</el-descriptions-item>
</template>
</template>
<el-descriptions-item label="申请事由" :span="3">
<div style="white-space: pre-line">{{ viewData.remark }}</div>
</el-descriptions-item>
<el-descriptions-item label="附件" :span="3">
<file-preview v-if="viewData.files && viewData.files.length > 0" :files="viewData.files"
complete_result></file-preview>
<span v-else>暂无附件</span>
</el-descriptions-item>
<el-descriptions-item label="签字" :span="3">
<el-image :src="viewData.signature"
v-if="viewData.signature"
class="signature-image"></el-image>
<span v-else>暂无签字</span>
</el-descriptions-item>
</el-descriptions>
<div v-if="viewData.recipients && viewData.recipients.length" class="process-title" style="display:flex;justify-content:space-between;align-items:center;gap:12px;flex-wrap:wrap;margin-top:20px">
<span>慰问对象明细 <el-tag size="small" effect="plain">{{ viewData.recipients.length }} 人</el-tag></span>
<span style="font-size:14px;font-weight:normal">慰问金额合计:<strong>¥ {{ detailTotal }}</strong></span>
</div>
<el-table v-if="viewData.recipients && viewData.recipients.length" :data="viewData.recipients" border stripe size="small" max-height="480" class="mt10" :header-cell-style="{ background: '#f5f7fa', color: '#606266' }">
<el-table-column type="index" label="序号" width="55" align="center" fixed="left"></el-table-column>
<el-table-column prop="helpUserName" label="慰问对象" width="130" fixed="left"></el-table-column>
<el-table-column prop="helpLoginName" label="工号" min-width="130"></el-table-column>
<el-table-column prop="idCard" label="身份证号码" min-width="190"></el-table-column>
<el-table-column prop="bankOfDeposit" label="开户行" min-width="180" show-overflow-tooltip></el-table-column>
<el-table-column prop="bankUserName" label="户名" min-width="150"></el-table-column>
<el-table-column prop="bankCardNumber" label="收款账户" min-width="210"></el-table-column>
</el-table>
<template v-for="task in doneTasks">
<div class="mt10">
<div class="process-title">{{ task.displayName }}</div>
<el-descriptions border class="flow-task-form" :column="3" :key="task.id"
v-if="task.ext.isFirstTaskNode">
<el-descriptions-item label="申请用户">{{ task.ext.initiatorName
}}({{task.ext.initiatorAccount}})
</el-descriptions-item>
<el-descriptions-item label="申请时间">{{ task.finishTime }}</el-descriptions-item>
<el-descriptions-item label="办理结果">
<dict-tag :options="dict.type.PROCESS_TASK_SUBMIT_TYPE"
:value="task.ext.submitType"></dict-tag>
</el-descriptions-item>
</el-descriptions>
<el-descriptions border class="flow-task-form" :column="3" :key="task.id" v-else>
<el-descriptions-item label="办理用户">{{ task.taskFormData.userName
}}({{task.taskFormData.loginName}})
</el-descriptions-item>
<el-descriptions-item label="办理时间">{{ task.finishTime }}</el-descriptions-item>
<el-descriptions-item label="办理结果">
<dict-tag :options="dict.type.PROCESS_TASK_SUBMIT_TYPE"
:value="task.ext.submitType"></dict-tag>
</el-descriptions-item>
<el-descriptions-item label="审批签字" :span="3" v-if="task.taskFormData && task.taskFormData.sign">
<el-image :src="task.taskFormData.sign" style="height: 80px" fit="contain"></el-image>
</el-descriptions-item>
<el-descriptions-item label="办理意见" v-if="!task.ext.isFirstTaskNode">{{
task.taskFormData.opinion }}
</el-descriptions-item>
</el-descriptions>
</div>
</template>
<slot></slot>
<snaker-chart ref="snakerChartRef"></snaker-chart>
</div>
`,
store,
dicts: ["PROCESS_TASK_SUBMIT_TYPE"],
data() {
return {
visible: false,
viewData: {},
doneTasks: [],
row: null
}
},
computed: {
detailTotal() {
const cents = (this.viewData.recipients || []).reduce((sum, row) => {
const money = Number(row.money)
return sum + (Number.isFinite(money) ? Math.round(money * 100) : 0)
}, 0)
return (cents / 100).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
}
},
methods: {
onOpen(row) {
this.row = row
this.visible = true
this.getInfo()
this.getDoneTasks()
},
getInfo() {
this.$axios.post('/platform/special_condolence/mine/info', {id: this.row.id}).then((res) => {
if (res.code === 0) {
this.viewData = res.data
}
})
},
// 获取已办任务审批记录
getDoneTasks() {
this.$axios.post("/flow/common/doneTasks", {bizId: this.row.id}).then((res) => {
if (res.code === 0) {
this.doneTasks = res.data
}
})
},
// 查看流程图
openChart(){
this.$refs.snakerChartRef.onOpenFull(this.row.instanceProcessDefineId, this.row.instanceId)
}
},
style: /*language=CSS*/ `
.el-descriptions-item__label {
width: 200px;
min-width: 200px;
max-width: 200px;
}
.el-tabs__header {
margin: 0;
}
`
}
@@ -0,0 +1,229 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app">
<guava ref="guava">
<el-card shadow="never">
<search @search="doSearch">
<search-item label="年度">
<el-date-picker
v-model="pageForm.year"
type="year"
value-format="yyyy"
placeholder="请选择年度"
style="width: 100%"
@change="doSearch"
></el-date-picker>
</search-item>
<search-item label="姓名/工号">
<el-input placeholder="请输入姓名或工号" clearable v-model="pageForm.searchKeyword"
@keyup.enter.native="doSearch">
</el-input>
</search-item>
<search-item label="慰问类型">
<el-select v-model="pageForm.type" @change="doSearch" style="width: 100%"
placeholder="请选择慰问类型" filterable clearable>
<el-option v-for="item in typeOptions"
:value="item.id"
:key="item.id"
:label="item.name"
></el-option>
</el-select>
</search-item>
<search-item label="所属工会">
<el-select v-model="pageForm.unionId" placeholder="请选择所属工会" filterable clearable
@change="doSearch" style="width: 100%">
<el-option
v-for="item in unionList"
:key="item.id"
:label="item.name"
:value="item.id">
</el-option>
</el-select>
</search-item>
<search-item label="所属单位">
<el-select @change="doSearch"
clearable
filterable
placeholder="请选择单位" style="width: 100%" v-model="pageForm.unitId">
<el-option :key="item.id" :label="item.name" :value="item.id" v-for="item in unitList"></el-option>
</el-select>
</search-item>
</search>
</el-card>
<el-card shadow="never">
<table-tool label="申请列表">
<el-radio-group v-model="pageForm.approval" size="small" @change="doSearch">
<el-radio-button :label="true">已审核</el-radio-button>
<el-radio-button :label="false">未审核</el-radio-button>
</el-radio-group>
</table-tool>
<el-table :data="tableData" @sort-change="pageOrder" style="width: 100%">
<el-table-column type="index" width="50px" label="序号" :index="indexMethod"></el-table-column>
<el-table-column
:label="column.label"
:prop="column.prop"
:key="column.prop"
:width="column.width"
:sortable="column.sortable"
align="center"
header-align="center"
show-overflow-tooltip
v-for="column in tableColumns"
>
<template v-slot="{ row }" v-if="column.prop === 'instanceState'">
<enum-tag :value="row.instanceState" name="ProcessInstanceStateEnum" label_key="message" size="small"></enum-tag>
</template>
</el-table-column>
<el-table-column label="操作" fixed="right" width="300px">
<template slot-scope="{row}">
<el-button @click="onView(row)" size="mini" type="primary">查看</el-button>
<el-button v-if="row.taskState === 10" @click="onAudit(row)" size="mini" type="primary">审核</el-button>
<el-button v-if="row.canRevoke" @click="onRevoke(row)" size="mini" type="danger">撤回</el-button>
</template>
</el-table-column>
</el-table>
<!--#include("/layouts/pagination.html"){}#-->
</el-card>
<template #edit>
<specialcondolence-info ref="specialcondolenceInfoRef">
<div v-if="showApprovalForm">
<div class="process-title">
{{formData.taskName}}
</div>
<el-form :model="formData" ref="formRef" :rules="formRules" label-width="0" label-suffix=":"
class="flow-task-form">
<el-form-item label="审批意见" prop="tf_opinion"
:rules="[{required:true,message:'必填',trigger:['change','blur']}]">
<user-opinion-textarea v-model="formData.tf_opinion"></user-opinion-textarea>
</el-form-item>
<el-form-item label="签字" prop="tf_sign" :rules="[{required:true,message:'请完成审批签字',trigger:['change','blur']}]">
<pc-signature v-model="formData.tf_sign" :key="formData.processTaskId"></pc-signature>
</el-form-item>
</el-form>
<el-row type="flex" justify="end">
<el-button @click="handleTaskAction(1)" size="small" type="primary">同意申请</el-button>
<el-button @click="handleTaskAction(2)" size="small" type="danger">拒绝申请</el-button>
<el-button @click="handleTaskAction(6)" size="small" type="info">退回到发起人</el-button>
<el-button @click="$refs.guava.index()" size="small">取消</el-button>
</el-row>
</div>
</specialcondolence-info>
</template>
</guava>
</div>
<script nonce="${cspNonce!}">
<!--#include('../common/info.js'){}#-->
new Vue({
el: "#app",
store,
mixins: [initTableMixins],
components: {
"specialcondolence-info": specialcondolenceInfo
},
data() {
return {
pageForm: {
approval: false
},
typeOptions: [],
tableColumns: [
{prop: 'helpUserName', label: '慰问对象'},
{prop: 'helpLoginName', label: '慰问对象工号'},
{prop: 'applyUserName', label: '申请人'},
{prop: 'applyUnionName', label: '所属工会'},
{prop: 'applyUnitName', label: '所属单位'},
{prop: 'typeName', label: '慰问类型'},
{prop: 'curTaskName', label: '当前节点'},
{prop: 'instanceState', label: '流程状态'},
],
unionList: [],
unitList: [],
formData: {},
showApprovalForm: false
}
},
methods: {
onView(row) {
this.$refs.guava.edit(()=>{
this.showApprovalForm = false
this.$refs.specialcondolenceInfoRef.onOpen(row)
})
},
onAudit(row) {
this.$refs.guava.edit(()=>{
this.showApprovalForm = true
this.formData = {
processTaskId: row.taskId,
taskName: row.curTaskName,
tf_sign: "",
tf_opinion: ""
}
this.$refs.specialcondolenceInfoRef.onOpen(row)
})
},
async handleTaskAction(val) {
try {
const valid = await this.$refs.formRef.validate()
if (!valid) return
} catch (error) {
return
}
this.$confirm("您确定要提交吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
this.$axios.post("/platform/special_condolence/approval/executeTask", {
data: JSON.stringify({
...this.formData,
submitType: val
})
}).then((res) => {
if (res.code === 0) {
this.$refs.guava.index()
this.$message.success(res.msg)
this.doSearch()
}
})
})
},
onRevoke(row) {
this.$confirm("您确定要撤回吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "info"
}).then(() => {
this.$axios.post("/flow/common/revokeTask", {taskId: row.taskId}).then((res) => {
if (res.code === 0) {
this.$message.success(res.msg)
this.doSearch()
}
})
})
},
querySpecialCondolenceType() {
this.$axios.post('/platform/special_condolence/type/querySpecialCondolenceType')
.then((resp) => {
this.typeOptions = resp.data
})
},
},
async created() {
this.unionList = await this.$businessTool.listUnion()
this.unitList = await this.$businessTool.listUnit()
this.querySpecialCondolenceType()
this.pageData()
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,170 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app">
<guava ref="guava">
<el-card shadow="never">
<search @search="doSearch">
<search-item label="年度">
<el-date-picker
v-model="pageForm.year"
type="year"
value-format="yyyy"
placeholder="请选择年度"
style="width: 100%"
@change="doSearch"
></el-date-picker>
</search-item>
<search-item label="姓名/工号">
<el-input placeholder="请输入姓名或工号" clearable v-model="pageForm.searchKeyword"
@keyup.enter.native="doSearch">
</el-input>
</search-item>
<search-item label="慰问类型">
<el-select v-model="pageForm.type" @change="doSearch" style="width: 100%"
placeholder="请选择慰问类型" filterable clearable>
<el-option v-for="item in typeOptions"
:value="item.id"
:key="item.id"
:label="item.name"
></el-option>
</el-select>
</search-item>
</search>
</el-card>
<el-card shadow="never">
<table-tool label="申请列表">
<el-button type="primary" size="small" @click="onAdd">
<i class="ti-plus"></i>
新增申请
</el-button>
</table-tool>
<el-table :data="tableData" @sort-change="pageOrder" style="width: 100%">
<el-table-column type="index" width="50px" label="序号" :index="indexMethod"></el-table-column>
<el-table-column
:label="column.label"
:prop="column.prop"
:key="column.prop"
:width="column.width"
:sortable="column.sortable"
align="center"
header-align="center"
show-overflow-tooltip
v-for="column in tableColumns"
>
<template v-slot="{ row }" v-if="column.prop === 'instanceState'">
<enum-tag :value="row.instanceState" name="ProcessInstanceStateEnum" label_key="message" size="small"></enum-tag>
</template>
</el-table-column>
<el-table-column label="操作" fixed="right" width="300">
<template slot-scope="{row}">
<el-button @click="onView(row)" size="mini" type="primary">查看</el-button>
<el-button v-if="row.taskKey === 'startTask' || !row.instanceId" @click="onEdit(row)" size="mini" type="primary">编辑</el-button>
<el-button v-if="row.canRevoke" size="mini" type="danger" @click="onRevoke(row)">撤回</el-button>
<el-dropdown v-if="row.createdBy === $store.state.user.id && row.instanceState == 20" trigger="click" @command="format => doExportApply(row, format)">
<el-button size="mini" type="primary">导出</el-button>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="pdf">导出 PDF</el-dropdown-item>
<el-dropdown-item command="word">导出 Word</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<el-button v-if="row.taskKey === 'startTask' || !row.instanceId" @click="onDelete(row.id)" size="mini" type="danger">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!--#include("/layouts/pagination.html"){}#-->
</el-card>
<template #view>
<specialcondolence-info ref="specialcondolenceInfoRef"></specialcondolence-info>
</template>
</guava>
</div>
<script nonce="${cspNonce!}">
<!--#include('../common/info.js'){}#-->
new Vue({
el: "#app",
store,
mixins: [initTableMixins],
components: {
"specialcondolence-info": specialcondolenceInfo
},
data() {
return {
typeOptions: [],
tableColumns: [
{prop: 'helpUserName', label: '慰问对象'},
{prop: 'helpLoginName', label: '慰问对象工号'},
{prop: 'applyUserName', label: '申请人'},
{prop: 'applyUnionName', label: '所属工会'},
{prop: 'applyUnitName', label: '所属单位'},
{prop: 'typeName', label: '慰问类型'},
{prop: 'taskName', label: '当前节点'},
{prop: 'instanceState', label: '流程状态'},
],
}
},
methods: {
doExportApply(row, format) {
this.$downLoad('/platform/special_condolence/mine/doExportApply?id=' + row.id + '&print=' + (format === 'pdf'))
},
onAdd() {
commonUtil.pjaxPush('/platform/special_condolence/apply')
},
onView(row) {
this.$refs.guava.view(()=>{
this.$refs.specialcondolenceInfoRef.onOpen(row)
})
},
onEdit(row) {
commonUtil.pjaxPush('/platform/special_condolence/apply?taskId=' + (row.startTaskId || '') + "&bizId=" + row.id)
},
onRevoke(row) {
this.$confirm("您确定要撤回吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
this.$axios.post("/flow/common/revokeTask", { taskId: row.startTaskId }).then((res) => {
if (res.code === 0) {
this.$message.success(res.msg)
this.pageData()
}
})
})
},
onDelete(id) {
this.$confirm("您确定要删除吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
this.$axios.post("/platform/special_condolence/mine/delete", { id }).then((res) => {
if (res.code === 0) {
this.$message.success(res.msg)
this.pageData()
}
})
})
},
querySpecialCondolenceType() {
this.$axios.post('/platform/special_condolence/type/querySpecialCondolenceType')
.then((resp) => {
this.typeOptions = resp.data
})
},
},
created() {
this.querySpecialCondolenceType()
this.pageData()
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,162 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app">
<guava ref="guava">
<el-card shadow="never">
<search @search="doSearch">
<search-item label="年度">
<el-date-picker
v-model="pageForm.year"
type="year"
value-format="yyyy"
placeholder="请选择年度"
style="width: 100%"
@change="doSearch"
></el-date-picker>
</search-item>
<search-item label="姓名/工号">
<el-input placeholder="请输入姓名或工号" clearable v-model="pageForm.searchKeyword"
@keyup.enter.native="doSearch">
</el-input>
</search-item>
<search-item label="慰问类型">
<el-select v-model="pageForm.type" @change="doSearch" style="width: 100%"
placeholder="请选择慰问类型" filterable clearable>
<el-option v-for="item in typeOptions"
:value="item.id"
:key="item.id"
:label="item.name"
></el-option>
</el-select>
</search-item>
<search-item label="所属工会">
<el-select v-model="pageForm.unionId" placeholder="请选择所属工会" filterable clearable
@change="doSearch" style="width: 100%">
<el-option
v-for="item in unionList"
:key="item.id"
:label="item.name"
:value="item.id">
</el-option>
</el-select>
</search-item>
<search-item label="所属单位">
<el-select @change="doSearch"
clearable
filterable
placeholder="请选择单位" style="width: 100%" v-model="pageForm.unitId">
<el-option :key="item.id" :label="item.name" :value="item.id" v-for="item in unitList"></el-option>
</el-select>
</search-item>
</search>
</el-card>
<el-card shadow="never">
<table-tool label="申请列表">
<el-button @click="onExport" icon="el-icon-s-promotion" type="primary" size="small">导出</el-button>
</table-tool>
<el-table :data="tableData" @sort-change="pageOrder" style="width: 100%">
<el-table-column type="index" width="50px" label="序号" :index="indexMethod"></el-table-column>
<el-table-column
:label="column.label"
:prop="column.prop"
:key="column.prop"
:width="column.width"
:sortable="column.sortable"
align="center"
header-align="center"
show-overflow-tooltip
v-for="column in tableColumns"
>
<template v-slot="{ row }" v-if="column.prop === 'instanceState'">
<enum-tag :value="row.instanceState" name="ProcessInstanceStateEnum" label_key="message" size="small"></enum-tag>
</template>
</el-table-column>
<el-table-column label="操作" fixed="right" width="180">
<template slot-scope="{row}">
<el-button @click="onView(row)" size="mini" type="primary">查看</el-button>
<el-button v-if="$auth.hasRoleOr('SYSADMIN, SCHOOL_UNION_ADMIN')" @click="onDelete(row.id)" size="mini" type="danger">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!--#include("/layouts/pagination.html"){}#-->
</el-card>
<template #edit>
<specialcondolence-info ref="specialcondolenceInfoRef"></specialcondolence-info>
</template>
</guava>
</div>
<script nonce="${cspNonce!}">
<!--#include('../common/info.js'){}#-->
new Vue({
el: "#app",
store,
mixins: [initTableMixins],
components: {
"specialcondolence-info": specialcondolenceInfo
},
data() {
return {
typeOptions: [],
tableColumns: [
{prop: 'helpUserName', label: '慰问对象'},
{prop: 'helpLoginName', label: '慰问对象工号'},
{prop: 'applyUserName', label: '申请人'},
{prop: 'applyUnionName', label: '所属工会'},
{prop: 'applyUnitName', label: '所属单位'},
{prop: 'typeName', label: '慰问类型'},
{prop: 'taskName', label: '当前节点'},
{prop: 'instanceState', label: '流程状态'},
],
unionList: [],
unitList: [],
}
},
methods: {
onDelete(id) {
this.$confirm("您确定要删除吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
this.$axios.post("/platform/special_condolence/mine/delete", { id }).then((res) => {
if (res.code === 0) {
this.$message.success(res.msg)
this.pageData()
}
})
})
},
onExport() {
this.$downLoad(loc() + '/onExport', this.pageForm)
},
onView(row) {
this.$refs.guava.edit(()=>{
this.$refs.specialcondolenceInfoRef.onOpen(row)
})
},
querySpecialCondolenceType() {
this.$axios.post('/platform/special_condolence/type/querySpecialCondolenceType')
.then((resp) => {
this.typeOptions = resp.data
})
},
},
async created() {
this.unionList = await this.$businessTool.listUnion()
this.unitList = await this.$businessTool.listUnit()
this.querySpecialCondolenceType()
this.pageData()
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,229 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app">
<guava ref="guava">
<el-card shadow="never">
<search @search="doSearch">
<search-item label="年度">
<el-date-picker
v-model="pageForm.year"
type="year"
value-format="yyyy"
placeholder="请选择年度"
style="width: 100%"
@change="doSearch"
></el-date-picker>
</search-item>
<search-item label="姓名/工号">
<el-input placeholder="请输入姓名或工号" clearable v-model="pageForm.searchKeyword"
@keyup.enter.native="doSearch">
</el-input>
</search-item>
<search-item label="慰问类型">
<el-select v-model="pageForm.type" @change="doSearch" style="width: 100%"
placeholder="请选择慰问类型" filterable clearable>
<el-option v-for="item in typeOptions"
:value="item.id"
:key="item.id"
:label="item.name"
></el-option>
</el-select>
</search-item>
<search-item label="所属工会">
<el-select v-model="pageForm.unionId" placeholder="请选择所属工会" filterable clearable
@change="doSearch" style="width: 100%">
<el-option
v-for="item in unionList"
:key="item.id"
:label="item.name"
:value="item.id">
</el-option>
</el-select>
</search-item>
<search-item label="所属单位">
<el-select @change="doSearch"
clearable
filterable
placeholder="请选择单位" style="width: 100%" v-model="pageForm.unitId">
<el-option :key="item.id" :label="item.name" :value="item.id" v-for="item in unitList"></el-option>
</el-select>
</search-item>
</search>
</el-card>
<el-card shadow="never">
<table-tool label="申请列表">
<el-radio-group v-model="pageForm.approval" size="small" @change="doSearch">
<el-radio-button :label="true">已审核</el-radio-button>
<el-radio-button :label="false">未审核</el-radio-button>
</el-radio-group>
</table-tool>
<el-table :data="tableData" @sort-change="pageOrder" style="width: 100%">
<el-table-column type="index" width="50px" label="序号" :index="indexMethod"></el-table-column>
<el-table-column
:label="column.label"
:prop="column.prop"
:key="column.prop"
:width="column.width"
:sortable="column.sortable"
align="center"
header-align="center"
show-overflow-tooltip
v-for="column in tableColumns"
>
<template v-slot="{ row }" v-if="column.prop === 'instanceState'">
<enum-tag :value="row.instanceState" name="ProcessInstanceStateEnum" label_key="message" size="small"></enum-tag>
</template>
</el-table-column>
<el-table-column label="操作" fixed="right" width="300px">
<template slot-scope="{row}">
<el-button @click="onView(row)" size="mini" type="primary">查看</el-button>
<el-button v-if="row.taskState === 10" @click="onAudit(row)" size="mini" type="primary">审核</el-button>
<el-button v-if="row.canRevoke" @click="onRevoke(row)" size="mini" type="danger">撤回</el-button>
</template>
</el-table-column>
</el-table>
<!--#include("/layouts/pagination.html"){}#-->
</el-card>
<template #edit>
<specialcondolence-info ref="specialcondolenceInfoRef">
<div v-if="showApprovalForm">
<div class="process-title">
{{formData.taskName}}
</div>
<el-form :model="formData" ref="formRef" :rules="formRules" label-width="0" label-suffix=":"
class="flow-task-form">
<el-form-item label="审批意见" prop="tf_opinion"
:rules="[{required:true,message:'必填',trigger:['change','blur']}]">
<user-opinion-textarea v-model="formData.tf_opinion"></user-opinion-textarea>
</el-form-item>
<el-form-item label="签字" prop="tf_sign" :rules="[{required:true,message:'请完成审批签字',trigger:['change','blur']}]">
<pc-signature v-model="formData.tf_sign" :key="formData.processTaskId"></pc-signature>
</el-form-item>
</el-form>
<el-row type="flex" justify="end">
<el-button @click="handleTaskAction(1)" size="small" type="primary">同意申请</el-button>
<el-button @click="handleTaskAction(2)" size="small" type="danger">拒绝申请</el-button>
<el-button @click="handleTaskAction(6)" size="small" type="info">退回到发起人</el-button>
<el-button @click="$refs.guava.index()" size="small">取消</el-button>
</el-row>
</div>
</specialcondolence-info>
</template>
</guava>
</div>
<script nonce="${cspNonce!}">
<!--#include('../common/info.js'){}#-->
new Vue({
el: "#app",
store,
mixins: [initTableMixins],
components: {
"specialcondolence-info": specialcondolenceInfo
},
data() {
return {
pageForm: {
approval: false
},
typeOptions: [],
tableColumns: [
{prop: 'helpUserName', label: '慰问对象'},
{prop: 'helpLoginName', label: '慰问对象工号'},
{prop: 'applyUserName', label: '申请人'},
{prop: 'applyUnionName', label: '所属工会'},
{prop: 'applyUnitName', label: '所属单位'},
{prop: 'typeName', label: '慰问类型'},
{prop: 'curTaskName', label: '当前节点'},
{prop: 'instanceState', label: '流程状态'},
],
unionList: [],
unitList: [],
formData: {},
showApprovalForm: false
}
},
methods: {
onView(row) {
this.$refs.guava.edit(()=>{
this.showApprovalForm = false
this.$refs.specialcondolenceInfoRef.onOpen(row)
})
},
onAudit(row) {
this.$refs.guava.edit(()=>{
this.showApprovalForm = true
this.formData = {
processTaskId: row.taskId,
taskName: row.curTaskName,
tf_sign: "",
tf_opinion: ""
}
this.$refs.specialcondolenceInfoRef.onOpen(row)
})
},
async handleTaskAction(val) {
try {
const valid = await this.$refs.formRef.validate()
if (!valid) return
} catch (error) {
return
}
this.$confirm("您确定要提交吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
this.$axios.post("/platform/special_condolence/approval/executeTask", {
data: JSON.stringify({
...this.formData,
submitType: val
})
}).then((res) => {
if (res.code === 0) {
this.$refs.guava.index()
this.$message.success(res.msg)
this.doSearch()
}
})
})
},
onRevoke(row) {
this.$confirm("您确定要撤回吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "info"
}).then(() => {
this.$axios.post("/flow/common/revokeTask", {taskId: row.taskId}).then((res) => {
if (res.code === 0) {
this.$message.success(res.msg)
this.doSearch()
}
})
})
},
querySpecialCondolenceType() {
this.$axios.post('/platform/special_condolence/type/querySpecialCondolenceType')
.then((resp) => {
this.typeOptions = resp.data
})
},
},
async created() {
this.unionList = await this.$businessTool.listUnion()
this.unitList = await this.$businessTool.listUnit()
this.querySpecialCondolenceType()
this.pageData()
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,229 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app">
<guava ref="guava">
<el-card shadow="never">
<search @search="doSearch">
<search-item label="年度">
<el-date-picker
v-model="pageForm.year"
type="year"
value-format="yyyy"
placeholder="请选择年度"
style="width: 100%"
@change="doSearch"
></el-date-picker>
</search-item>
<search-item label="姓名/工号">
<el-input placeholder="请输入姓名或工号" clearable v-model="pageForm.searchKeyword"
@keyup.enter.native="doSearch">
</el-input>
</search-item>
<search-item label="慰问类型">
<el-select v-model="pageForm.type" @change="doSearch" style="width: 100%"
placeholder="请选择慰问类型" filterable clearable>
<el-option v-for="item in typeOptions"
:value="item.id"
:key="item.id"
:label="item.name"
></el-option>
</el-select>
</search-item>
<search-item label="所属工会">
<el-select v-model="pageForm.unionId" placeholder="请选择所属工会" filterable clearable
@change="doSearch" style="width: 100%">
<el-option
v-for="item in unionList"
:key="item.id"
:label="item.name"
:value="item.id">
</el-option>
</el-select>
</search-item>
<search-item label="所属单位">
<el-select @change="doSearch"
clearable
filterable
placeholder="请选择单位" style="width: 100%" v-model="pageForm.unitId">
<el-option :key="item.id" :label="item.name" :value="item.id" v-for="item in unitList"></el-option>
</el-select>
</search-item>
</search>
</el-card>
<el-card shadow="never">
<table-tool label="申请列表">
<el-radio-group v-model="pageForm.approval" size="small" @change="doSearch">
<el-radio-button :label="true">已审核</el-radio-button>
<el-radio-button :label="false">未审核</el-radio-button>
</el-radio-group>
</table-tool>
<el-table :data="tableData" @sort-change="pageOrder" style="width: 100%">
<el-table-column type="index" width="50px" label="序号" :index="indexMethod"></el-table-column>
<el-table-column
:label="column.label"
:prop="column.prop"
:key="column.prop"
:width="column.width"
:sortable="column.sortable"
align="center"
header-align="center"
show-overflow-tooltip
v-for="column in tableColumns"
>
<template v-slot="{ row }" v-if="column.prop === 'instanceState'">
<enum-tag :value="row.instanceState" name="ProcessInstanceStateEnum" label_key="message" size="small"></enum-tag>
</template>
</el-table-column>
<el-table-column label="操作" fixed="right" width="300px">
<template slot-scope="{row}">
<el-button @click="onView(row)" size="mini" type="primary">查看</el-button>
<el-button v-if="row.taskState === 10" @click="onAudit(row)" size="mini" type="primary">审核</el-button>
<el-button v-if="row.canRevoke" @click="onRevoke(row)" size="mini" type="danger">撤回</el-button>
</template>
</el-table-column>
</el-table>
<!--#include("/layouts/pagination.html"){}#-->
</el-card>
<template #edit>
<specialcondolence-info ref="specialcondolenceInfoRef">
<div v-if="showApprovalForm">
<div class="process-title">
{{formData.taskName}}
</div>
<el-form :model="formData" ref="formRef" :rules="formRules" label-width="0" label-suffix=":"
class="flow-task-form">
<el-form-item label="审批意见" prop="tf_opinion"
:rules="[{required:true,message:'必填',trigger:['change','blur']}]">
<user-opinion-textarea v-model="formData.tf_opinion"></user-opinion-textarea>
</el-form-item>
<el-form-item label="签字" prop="tf_sign" :rules="[{required:true,message:'请完成审批签字',trigger:['change','blur']}]">
<pc-signature v-model="formData.tf_sign" :key="formData.processTaskId"></pc-signature>
</el-form-item>
</el-form>
<el-row type="flex" justify="end">
<el-button @click="handleTaskAction(1)" size="small" type="primary">同意申请</el-button>
<el-button @click="handleTaskAction(2)" size="small" type="danger">拒绝申请</el-button>
<el-button @click="handleTaskAction(6)" size="small" type="info">退回到发起人</el-button>
<el-button @click="$refs.guava.index()" size="small">取消</el-button>
</el-row>
</div>
</specialcondolence-info>
</template>
</guava>
</div>
<script nonce="${cspNonce!}">
<!--#include('../common/info.js'){}#-->
new Vue({
el: "#app",
store,
mixins: [initTableMixins],
components: {
"specialcondolence-info": specialcondolenceInfo
},
data() {
return {
pageForm: {
approval: false
},
typeOptions: [],
tableColumns: [
{prop: 'helpUserName', label: '慰问对象'},
{prop: 'helpLoginName', label: '慰问对象工号'},
{prop: 'applyUserName', label: '申请人'},
{prop: 'applyUnionName', label: '所属工会'},
{prop: 'applyUnitName', label: '所属单位'},
{prop: 'typeName', label: '慰问类型'},
{prop: 'curTaskName', label: '当前节点'},
{prop: 'instanceState', label: '流程状态'},
],
unionList: [],
unitList: [],
formData: {},
showApprovalForm: false
}
},
methods: {
onView(row) {
this.$refs.guava.edit(()=>{
this.showApprovalForm = false
this.$refs.specialcondolenceInfoRef.onOpen(row)
})
},
onAudit(row) {
this.$refs.guava.edit(()=>{
this.showApprovalForm = true
this.formData = {
processTaskId: row.taskId,
taskName: row.curTaskName,
tf_sign: "",
tf_opinion: ""
}
this.$refs.specialcondolenceInfoRef.onOpen(row)
})
},
async handleTaskAction(val) {
try {
const valid = await this.$refs.formRef.validate()
if (!valid) return
} catch (error) {
return
}
this.$confirm("您确定要提交吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
this.$axios.post("/platform/special_condolence/approval/executeTask", {
data: JSON.stringify({
...this.formData,
submitType: val
})
}).then((res) => {
if (res.code === 0) {
this.$refs.guava.index()
this.$message.success(res.msg)
this.doSearch()
}
})
})
},
onRevoke(row) {
this.$confirm("您确定要撤回吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "info"
}).then(() => {
this.$axios.post("/flow/common/revokeTask", {taskId: row.taskId}).then((res) => {
if (res.code === 0) {
this.$message.success(res.msg)
this.doSearch()
}
})
})
},
querySpecialCondolenceType() {
this.$axios.post('/platform/special_condolence/type/querySpecialCondolenceType')
.then((resp) => {
this.typeOptions = resp.data
})
},
},
async created() {
this.unionList = await this.$businessTool.listUnion()
this.unitList = await this.$businessTool.listUnit()
this.querySpecialCondolenceType()
this.pageData()
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,101 @@
const basicForm = {
template: /*language=HTML*/ `
<div>
<el-form :model="formData" ref="formRef" :rules="formRules" label-width="110px" label-position="left">
<el-form-item label="类型编码" prop="code">
<el-input type="text" v-model="formData.code" maxlength="50"
placeholder="请填写类型编码"></el-input>
</el-form-item>
<el-form-item label="类型名称" prop="name">
<el-input type="text" v-model="formData.name" maxlength="50"
placeholder="请填写类型名称"></el-input>
</el-form-item>
<el-form-item label="慰问费用" prop="money">
<el-input-number style="width: 100%" v-model="formData.money"
placeholder="请填写慰问费用" controls-position="right" :controls="false"
precision="2"
:min="0"
:max="10000"></el-input-number>
</el-form-item>
<el-form-item label="慰问方式" prop="way">
<el-radio-group v-model="formData.way">
<el-radio-button label="慰问金 (A)" border>慰问金 (A)</el-radio-button>
<el-radio-button label="提货券 (B)" border>提货券 (B)</el-radio-button>
<el-radio-button label="慰问品 (C)" border>慰问品 (C)</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item label="是否启用" prop="enable">
<el-switch
v-model="formData.enable"
active-color="#13ce66"
inactive-color="#ff4949">
</el-switch>
</el-form-item>
<!--<el-form-item label="上传会议纪要" prop="uploadMeetingRecord">
<el-radio-group v-model="formData.uploadMeetingRecord">
<el-radio-button :label="true">上传</el-radio-button>
<el-radio-button :label="false">不上传</el-radio-button>
</el-radio-group>
</el-form-item>-->
<el-form-item label="是否上传附件" prop="isUploadFile">
<el-radio-group v-model="formData.isUploadFile">
<el-radio-button :label="true">是</el-radio-button>
<el-radio-button :label="false">否</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item label="上传附件说明" prop="uploadFileDesc" v-if="formData.isUploadFile">
<el-input v-model="formData.uploadFileDesc" maxlength="50" clearable placeholder="请填写上传附件说明"></el-input>
</el-form-item>
</el-form>
<el-row class="mt10" justify="end" type="flex">
<el-button @click="$emit('refresh')">取消</el-button>
<el-button @click="onSubmit" type="primary">提交</el-button>
</el-row>
</div>
`,
data() {
return {
formData: { isUploadFile: false },
formRules: {
name: [{required: true, message: '必填', trigger: ['blur', 'change']}],
money: [{required: true, message: '必填', trigger: ['blur', 'change']}],
way: [{required: true, message: '必填', trigger: ['blur', 'change']}],
code: [{required: true, message: '必填', trigger: ['blur', 'change']}],
isUploadFile: [{required: true, message: '必填', trigger: ['blur', 'change']}],
},
}
},
methods: {
onOpen(row) {
if(row && row.id) {
this.formData = clone(row)
} else {
this.formData = { isUploadFile: false }
}
},
onSubmit() {
this.$refs.formRef.validate((valid) => {
if (valid) {
this.$confirm("您确定要提交吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(async () => {
const resp = await this.$axios.post("/platform/special_condolence/type/onSubmit", this.formData)
if (resp.code === 0) {
this.$message.success(resp.msg)
this.$emit('refresh')
} else {
this.$message.warning(resp.msg)
}
})
}
})
},
},
style: /*language=CSS*/ `
.el-input-number .el-input__inner {
text-align: left;
}
`
}
@@ -0,0 +1,148 @@
<!--#
layout("/layouts/platform.html"){
#-->
<style>
</style>
<div id="app" v-cloak>
<guava ref="guava">
<template>
<el-card shadow="never">
<search @search="doSearch">
<search-item label="名称/编码">
<el-input placeholder="请输入名称或编码" clearable v-model="pageForm.searchKeyword"
@keyup.enter.native="doSearch">
</el-input>
</search-item>
</search>
</el-card>
<el-card shadow="never" class="mt20">
<table-tool label="类型列表">
<el-button type="primary" size="small" @click="onAdd">
<i class="ti-plus"></i>
新增类型
</el-button>
</table-tool>
<el-table :data="tableData" @sort-change="pageOrder" :size="tableSize">
<el-table-column label="序号" type="index" :index="indexMethod" width="60"></el-table-column>
<el-table-column
:label="column.label"
:prop="column.prop"
:key="column.prop"
:width="column.width"
:sortable="column.sortable"
align="center"
header-align="center"
show-overflow-tooltip
v-for="column in tableColumns"
>
<template v-slot="{ row }" v-if="column.prop === 'isUploadFile'">
<span v-if="row.isUploadFile" class="text-success">是</span>
<span v-else class="text-danger">否</span>
</template>
<template v-slot="{ row }" v-else-if="column.prop === 'enable'">
<el-switch
@change="switchChange(row)"
v-model="row.enable"
active-color="#13ce66"
inactive-color="#ff4949">
</el-switch>
</template>
</el-table-column>
<el-table-column label="操作" width="230">
<template v-slot="{ row }">
<el-button @click="onEdit(row)" size="mini" type="primary">编辑</el-button>
<el-button @click="onDelete(row)" size="mini" type="danger">删除</el-button>
</template>
</el-table-column>
</el-table>
<!--#include("/layouts/pagination.html"){}#-->
</el-card>
</template>
<template #edit>
<basic-form ref="basicFormRef" @refresh="refresh"></basic-form>
</template>
</guava>
</div>
<script nonce="${cspNonce!}">
<!--#include('basicForm.js'){}#-->
const vue = new Vue({
el: "#app",
mixins: [initTableMixins],
components: {
"basic-form": basicForm,
},
data() {
return {
tableColumns: [
{prop: 'code', label: '类型编码'},
{prop: 'name', label: '类型名称'},
{prop: 'money', label: '金额'},
{prop: 'way', label: '慰问方式'},
// {prop: 'isUploadFile', label: '是否上传附件'},
{prop: 'enable', label: '是否启用'},
],
}
},
methods: {
refresh() {
this.doSearch()
this.$refs.guava.index()
},
onAdd() {
this.$refs.guava.edit(() => {
this.$refs.basicFormRef.onOpen()
})
},
onEdit(row) {
this.$refs.guava.edit(() => {
this.$refs.basicFormRef.onOpen(row)
})
},
onDelete(row) {
this.$confirm("您确定要删除吗, 是否继续?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
})
.then(() => {
this.$axios.post("/platform/special_condolence/type/onDelete", { id: row.id }).then((res) => {
if (res.code === 0) {
this.$message.success(res.msg)
this.pageData()
}
})
})
.catch(() => {})
},
switchChange(row) {
this.$axios.post("/platform/special_condolence/type/onSubmit", row).then((res) => {
if (res.code === 0) {
this.$message.success(res.msg)
this.pageData()
}
})
},
pageData() {
this.$axios.post(loc() + "/pageData", this.pageForm).then((res) => {
if (res.code === 0) {
this.tableData = res.data.list
this.pageForm.totalCount = res.data.totalCount
}
})
},
},
async created() {
this.pageData()
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,513 @@
<!--#
layout("/layouts/platform_h5.html"){
#-->
<style scoped>
#app {
--color-primary: #1989fa;
}
#app > .van-nav-bar .van-icon,
#app > .van-nav-bar .van-nav-bar__text {
color: #1989fa;
}
#app > .van-nav-bar .van-nav-bar__title,
#app > .van-nav-bar .van-nav-bar__text,
#app > .van-nav-bar .van-nav-bar__arrow {
font-size: 18px;
}
.search-dialog {
height: 80%;
}
/* 慰问申请页复用提案撰写页的 Card 层级,仅覆盖当前表单分组和底部操作栏。 */
.specialcondolence-apply-form.form-container {
min-height: 100%;
padding: 12px 15px calc(84px + env(safe-area-inset-bottom));
box-sizing: border-box;
background: #f3f7fc;
}
.specialcondolence-apply-form .form-section {
margin-bottom: 11px;
overflow: hidden;
border: 1px solid #e9eff7;
border-radius: 10px;
background: #fff;
box-shadow: 0 3px 11px rgba(51, 87, 126, 0.08);
}
.specialcondolence-apply-form .specialcondolence-card__title {
position: relative;
margin: 0;
padding: 12px 14px 8px 21px;
color: #354255;
font-size: 15px;
line-height: 20px;
font-weight: 600;
background: transparent;
}
.specialcondolence-apply-form .specialcondolence-card__title::before {
position: absolute;
top: 15px;
bottom: 11px;
left: 12px;
width: 2px;
border-radius: 2px;
background: #1989fa;
content: "";
}
.specialcondolence-apply-form .form-section .van-cell-group {
margin: 0;
background: transparent;
}
.specialcondolence-card__desc {
padding: 0 14px 8px 21px;
color: orangered;
font-size: 14px;
line-height: 18px;
font-weight: normal;
}
.specialcondolence-apply-form .form-section .van-cell {
min-height: 44px;
padding: 10px 14px;
background: transparent;
}
.specialcondolence-apply-form .form-section .van-cell:not(:last-child)::after {
right: 14px;
left: 14px;
border-color: #eef2f7;
}
.specialcondolence-apply-form .form-section .van-field__label {
width: 84px;
color: #596779;
font-size: 14px;
line-height: 24px;
}
.specialcondolence-apply-form .form-section .van-field__value,
.specialcondolence-apply-form .form-section .van-field__control {
color: #4f5d70;
font-size: 14px;
line-height: 24px;
}
.specialcondolence-apply-form .form-section .van-field__control::placeholder {
color: #b1bac6;
}
.specialcondolence-apply-form .form-section .van-cell--required::before {
left: 6px;
color: #f06464;
}
.specialcondolence-apply-actions {
position: fixed;
z-index: 100;
right: 0;
bottom: 0;
left: 0;
display: flex;
gap: 9px;
padding: 10px 18px calc(10px + env(safe-area-inset-bottom));
border-top: 1px solid #edf1f6;
background: rgba(255, 255, 255, 0.98);
box-shadow: 0 -4px 14px rgba(50, 78, 112, 0.06);
}
.specialcondolence-apply-actions .van-button {
height: 40px;
margin: 0;
border-radius: 14px !important;
font-size: 16px;
}
.specialcondolence-apply-actions .van-button:first-child {
flex: 1;
color: #1989fa;
border-color: #1989fa;
background: #fff;
}
.specialcondolence-apply-actions .van-button:last-child {
flex: 1.08;
border-color: #1989fa;
background: #1989fa;
}
#app .van-action-sheet__header,
#app .van-picker__title,
.van-dialog__header {
font-size: 18px;
}
#app .van-action-sheet__name,
#app .van-action-sheet__cancel,
#app .van-empty__description,
#app .van-search__field,
#app .van-picker__cancel,
#app .van-picker__confirm,
.van-dialog__message {
font-size: 16px;
}
#app .van-picker-column__item {
font-size: 18px;
}
</style>
<div id="app">
<!-- 导航栏 -->
<van-nav-bar title="特殊慰问申请" left-text="返回" left-arrow @click-left="historyBack" fixed
placeholder></van-nav-bar>
<!-- 表单容器 -->
<van-form ref="formRef" class="form-container specialcondolence-apply-form">
<!-- 基本信息 -->
<section class="form-section">
<h2 class="specialcondolence-card__title">基本信息</h2>
<van-cell-group :border="false">
<van-field label="申请人姓名" :rules="[{ required: true }]" v-model="formData.applyUserName" readonly
required name="applyUserName"></van-field>
<van-field label="申请人工号" :rules="[{ required: true }]" v-model="formData.applyLoginName" readonly
required name="applyLoginName"></van-field>
<van-field label="申请人单位" :rules="[{ required: true }]" v-model="formData.applyUnitName" readonly
required name="applyUnitName"></van-field>
<van-field label="申请人工会" :rules="[{ required: true }]" v-model="formData.applyUnionName" readonly
required name="applyUnionName"></van-field>
<van-field label="慰问类型" required><template #input><select v-model="formData.type" @change="typeChange(formData.type)"><option value="">请选择</option><option v-for="t in typeOptions" :key="t.id" :value="t.id">{{t.name}}</option></select></template></van-field>
<van-field v-model="formData.perMoney" label="每人金额" type="number" required :rules="[{ validator: validateMoney, message: '金额须大于0且不超过10000元,最多两位小数' }]"></van-field>
<van-field v-model="formData.occurTime" label="慰问时间" placeholder="yyyy-MM-dd" required :rules="[{required:true}]"></van-field>
<div v-for="(person, index) in formData.recipients" :key="index">
<van-cell :title="'慰问对象 ' + (index + 1)"><van-button native-type="button" size="mini" @click="formData.recipients.splice(index, 1)">删除</van-button></van-cell>
<van-field v-model="person.helpUserName" label="慰问对象" required :rules="[{required:true}]"></van-field>
<van-field v-model="person.helpLoginName" label="工号" placeholder="普通人员必填,劳务派遣可空"></van-field>
<van-field v-model="person.idCard" label="身份证号码" maxlength="18" required :rules="[{required:true}]"></van-field>
<van-field v-model="person.bankOfDeposit" label="开户行" maxlength="100" required :rules="[{required:true}]"></van-field>
<van-field v-model="person.bankUserName" label="户名" maxlength="100" required :rules="[{required:true}]"></van-field>
<van-field v-model="person.bankCardNumber" label="收款账户" maxlength="30" required :rules="[{required:true}]"></van-field>
</div>
<van-button native-type="button" @click="formData.recipients.push(newRecipient())">增加一行</van-button>
<van-field
v-model="formData.remark"
name="remark"
maxlength="500"
label="申请事由"
type="textarea"
rows="4"
autosize
placeholder="请输入申请事由"
:rules="[{ required: true }]"
required
></van-field>
</van-cell-group>
</section>
<section class="form-section" v-if="chooseType.isUploadFile === true">
<h2 class="specialcondolence-card__title">附件材料</h2>
<div v-if="chooseType.isUploadFile && chooseType.uploadFileDesc" class="specialcondolence-card__desc">
{{ '附件说明:' + chooseType.uploadFileDesc }}
</div>
<van-cell-group :border="false">
<van-field class="direction-column-field" name="files" label="附件" required
:rules="[{ validator: () => Array.isArray(formData.files) && formData.files.length > 0 && formData.files.length <= 5, message: '请上传1至5个附件' }]">
<template #input>
<h5-file-upload
slot="input"
:value.sync="formData.files"
:upload_number="5"
upload_text="上传附件"
upload_result_category="array"
complete_result
></h5-file-upload>
<div class="specialcondolence-card__desc">最多上传 5 个文件</div>
</template>
</van-field>
</van-cell-group>
</section>
<section class="form-section">
<h2 class="specialcondolence-card__title">签字确认</h2>
<van-cell-group :border="false">
<van-field class="direction-column-field" name="signature" label="" :value="formData.signature"
:rules="[{ required: true, message: '请填写签字' }]">
<template #input>
<h5-signature v-model="formData.signature" slot="input"></h5-signature>
</template>
</van-field>
</van-cell-group>
</section>
<!-- 提交按钮 -->
<div class="form-actions specialcondolence-apply-actions">
<van-button native-type="button" @click="onSave" round type="info" plain>保存申请</van-button>
<van-button @click="onSubmit" round type="info" v-if="!taskId">提交申请</van-button>
<van-button @click="onFinishTask" round type="info" v-else>提交申请</van-button>
</div>
</van-form>
</div>
<script nonce="${cspNonce!}">
<!--#include("/platform/zhgh/staffbenefit/specialcondolence/apply/recipients.js"){}#-->
const vue = new Vue({
el: "#app",
store,
mixins: [specialCondolenceRecipients],
components: {},
data() {
return {
bizId: GetQueryString("bizId"),
taskId: GetQueryString("taskId"),
formData: { recipients: [] },
chooseType: {},
userOptions: [],
typeOptions: [],
helpUserSelectShow: false,
payUserSelectShow: false,
showTypePicker: false,
typeColumns: [],
searchKeyword: "",
showTimePicker: false,
showChildPicker: false,
showFamilyPicker: false,
certifierUserSelectShow: false,
handlerUserSelectShow: false,
showHospitalizationTimePicker: false,
showLeaveHospitalTimePicker: false,
minDate: new Date(2024, 0, 1),
maxDate: new Date(),
typeDefaultIndex: 0
}
},
methods: {
validateMoney(value) {
return /^(0|[1-9][0-9]*)(\.[0-9]{1,2})?$/.test(String(value)) && Number(value) > 0 && Number(value) <= 10000
},
// 选择出院时间
showLeaveHospitalTimePickerClick() {
if (this.formData.leaveHospitalTime) {
this.formData.leaveHospitalDate = new Date(this.formData.leaveHospitalTime)
}
this.showLeaveHospitalTimePicker = true
},
// 选择住院时间
showHospitalizationTimePickerClick() {
if (this.formData.hospitalizationTime) {
this.formData.hospitalizationDate = new Date(this.formData.hospitalizationTime)
}
this.showHospitalizationTimePicker = true
},
// 选择慰问时间
showTimePickerClick() {
if (this.formData.occurTime) {
this.formData.occurDate = new Date(this.formData.occurTime)
}else{
this.formData.occurDate = new Date()
}
this.showTimePicker = true
},
// 选择慰问类型
showTypePickerClick() {
if (this.formData.type) {
this.typeDefaultIndex = this.typeOptions.findIndex(item => item.id === this.formData.type)
}
this.showTypePicker = true
},
//经办人
handlerUserChange(user) {
const { id, userName, loginName, unitId, unitName, unionId, unionName } = user
this.$set(this.formData, "handlerUserId", id)
this.$set(this.formData, "handlerUserName", userName)
this.$set(this.formData, "handlerLoginName", loginName)
this.$set(this.formData, "handlerUnitId", unitId)
this.$set(this.formData, "handlerUnitName", unitName)
this.$set(this.formData, "handlerUnionId", unionId)
this.$set(this.formData, "handlerUnionName", unionName)
this.handlerUserSelectShow = false
this.searchKeyword = ""
this.userOptions = []
},
//证明人
certifierUserChange(user) {
const { id, userName, loginName, unitId, unitName, unionId, unionName } = user
this.$set(this.formData, "certifierUserId", id)
this.$set(this.formData, "certifierUserName", userName)
this.$set(this.formData, "certifierLoginName", loginName)
this.$set(this.formData, "certifierUnitId", unitId)
this.$set(this.formData, "certifierUnitName", unitName)
this.$set(this.formData, "certifierUnionId", unionId)
this.$set(this.formData, "certifierUnionName", unionName)
this.certifierUserSelectShow = false
this.searchKeyword = ""
this.userOptions = []
},
async userRemoteMethod(event, type) {
if (event) {
this.userOptions = await this.selectQueryUser(event)
if (type === "help") {
this.helpUserSelectShow = true
} else if (type === "certifier") {
this.certifierUserSelectShow = true
} else if (type === "handler") {
this.handlerUserSelectShow = true
}
}
},
async selectQueryUser(keyword) {
const res = await this.$axios.post("/platform/special_condolence/apply/listUser", { keyword: keyword })
return res.data
},
helpUserChange(user) {
const { id, userName, loginName, unitId, unitName, unionId, unionName } = user
this.$set(this.formData, "helpUserId", id)
this.$set(this.formData, "helpUserName", userName)
this.$set(this.formData, "helpLoginName", loginName)
this.$set(this.formData, "helpUnitId", unitId)
this.$set(this.formData, "helpUnitName", unitName)
this.$set(this.formData, "helpUnionId", unionId)
this.$set(this.formData, "helpUnionName", unionName)
this.helpUserSelectShow = false
this.searchKeyword = ""
this.userOptions = []
},
payUserChange(user) {
const { id, userName, loginName } = user
this.$set(this.formData, "payUserId", id)
this.$set(this.formData, "payUserName", userName)
this.$set(this.formData, "payLoginName", loginName)
this.payUserSelectShow = false
this.searchKeyword = ""
this.userOptions = []
},
typeChange(id) {
this.chooseType = this.typeOptions.find(o => o.id === id)
if (this.chooseType) {
this.$set(this.formData, "perMoney", this.chooseType.money)
this.$set(this.formData, "way", this.chooseType.way)
this.$set(this.formData, "typeCode", this.chooseType.code)
}
},
onTypeConfirm(o) {
this.$set(this.formData, "typeName", o.text)
this.$set(this.formData, "type", o.value)
this.typeChange(o.value)
this.showTypePicker = false
},
onSave() {
this.$dialog.confirm({
title: "提示",
message: "您确定保存吗?"
}).then(() => {
this.$axios.post("/platform/special_condolence/apply/save", { data: JSON.stringify(this.formData) }).then(res => {
if (res.code === 0) {
this.$toast(res.msg)
this.$pjaxReplace("/platform/special_condolence/mine/h5")
}
})
})
},
async onSubmit() {
this.$refs.formRef.validate().then(() => {
if (!this.formData.signature){
this.$toast.fail("请填写签字")
return
}
this.$dialog.confirm({
title: "提示",
message: "您确定要提交申请吗?"
}).then(() => {
this.$axios.post("/platform/special_condolence/apply/submit", { data: JSON.stringify(this.formData) }).then(res => {
if (res.code === 0) {
this.$toast(res.msg)
this.$pjaxReplace("/platform/special_condolence/mine/h5")
}
})
})
})
},
async onFinishTask() {
this.$refs.formRef.validate().then(() => {
if (!this.formData.signature){
this.$toast.fail("请填写签字")
return
}
this.$dialog.confirm({
title: "提示",
message: "您确定要提交申请吗?"
}).then(() => {
this.$axios.post("/platform/special_condolence/apply/submitAgain", {
data: JSON.stringify(this.formData),
taskId: GetQueryString("taskId")
}).then(res => {
if (res.code === 0) {
this.$toast(res.msg)
this.$pjaxReplace("/platform/special_condolence/mine/h5")
}
})
})
})
},
init() {
if (this.bizId) {
this.$axios.post("/platform/special_condolence/mine/info", { id: this.bizId }).then((res) => {
if (res.code === 0) {
this.formData = res.data
this.initRecipients()
this.selectQueryUser(this.formData.helpLoginName, this.helpUserOptions)
this.selectQueryUser(this.formData.payLoginName, this.payUserOptions)
this.chooseType = this.typeOptions.find(o => o.id === this.formData.type)
}
})
} else {
const { username, loginname, id, unit, union, mobile } = this.$store.state.user
this.formData = {
recipients: [this.newRecipient()],
perMoney: undefined,
occurTime: this.$moment().format('YYYY-MM-DD'),
applyUserName: username,
applyLoginName: loginname,
applyUserId: id,
applyUnitId: unit?.id,
applyUnitName: unit?.name,
applyUnionId: union?.id,
applyUnionName: union?.name
}
}
},
querySpecialCondolenceType() {
this.$axios.post("/platform/special_condolence/type/querySpecialCondolenceType")
.then((res) => {
this.typeOptions = JSON.parse(JSON.stringify(res.data))
res.data.forEach((v) => {
this.typeColumns.push({ value: v.id, text: v.name })
})
})
}
},
created() {
this.querySpecialCondolenceType()
this.init()
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,201 @@
.specialcondolence-full-popup {
--color-primary: #1989fa;
width: 100%;
height: 100%;
overflow: hidden;
background: #f3f7fc;
}
.specialcondolence-full-popup__page {
display: flex;
height: 100%;
padding-top: 46px;
overflow: hidden;
box-sizing: border-box;
flex-direction: column;
}
.specialcondolence-full-popup__page > .van-nav-bar {
z-index: 20;
background: #fff;
}
.specialcondolence-full-popup__page > .van-nav-bar .van-icon,
.specialcondolence-full-popup__page > .van-nav-bar .van-nav-bar__text {
color: #1989fa;
}
.specialcondolence-full-popup__scroll {
min-height: 0;
overflow-y: auto;
flex: 1;
-webkit-overflow-scrolling: touch;
}
.specialcondolence-info-main {
padding: 12px 15px calc(18px + env(safe-area-inset-bottom));
}
.specialcondolence-info-card {
margin-bottom: 11px;
overflow: hidden;
border: 1px solid #e9eff7;
border-radius: 10px;
background: #fff;
box-shadow: 0 3px 11px rgba(51, 87, 126, .08);
}
.specialcondolence-info-card__title {
position: relative;
margin: 0;
padding: 12px 14px 8px 21px;
color: #354255;
font-size: 15px;
font-weight: 600;
line-height: 22px;
}
.specialcondolence-info-card__title::before {
position: absolute;
top: 15px;
bottom: 11px;
left: 12px;
width: 2px;
border-radius: 2px;
background: #1989fa;
content: "";
}
/* 审核意见复用详情 Card 标题,并与提案 delegation 审核输入区保持一致。 */
.specialcondolence-audit-card__required {
margin-left: 4px;
color: #ee0a24;
font-size: 14px;
}
.specialcondolence-info-card .van-field.specialcondolence-audit-field {
width: calc(100% - 28px);
margin: 0 14px 14px;
padding: 12px;
box-sizing: border-box;
border: 1px solid #c9d5e5;
border-radius: 10px;
background: #f7f9fd;
}
.specialcondolence-info-card .van-field.specialcondolence-audit-field .van-field__control {
min-height: 150px;
color: #253247;
line-height: 22px;
}
.specialcondolence-info-card .van-field.specialcondolence-audit-field .van-field__control::placeholder {
color: #9aa7b8;
}
.specialcondolence-info-card .van-field.specialcondolence-audit-field .van-field__word-limit {
color: #718096;
}
.specialcondolence-info-card .van-field.specialcondolence-audit-field::after {
display: none;
}
.specialcondolence-popup-actions {
display: flex;
padding: 10px 12px calc(12px + env(safe-area-inset-bottom));
justify-content: space-between;
column-gap: 10px;
background: transparent;
}
.specialcondolence-popup-actions .van-button {
border-radius: 14px;
}
.specialcondolence-popup-actions .specialcondolence-action-primary {
border-color: #1989fa;
background: #1989fa;
}
.specialcondolence-popup-actions .specialcondolence-action-danger {
border-color: #ff4d4f;
background: #ff4d4f;
}
.specialcondolence-info-card .van-cell-group {
margin: 0;
background: transparent;
}
.specialcondolence-info-card .van-cell {
min-height: 44px;
padding: 10px 14px;
background: transparent;
}
.specialcondolence-info-card .van-cell:not(:last-child)::after {
right: 14px;
left: 14px;
border-color: #eef2f7;
}
.specialcondolence-info-card .van-cell__title {
width: 102px;
color: #596779;
font-size: 12px;
line-height: 24px;
flex: 0 0 102px;
}
.specialcondolence-info-card .van-cell__value {
min-width: 0;
color: #4f5d70;
font-size: 12px;
line-height: 24px;
text-align: left;
overflow-wrap: anywhere;
}
.specialcondolence-info-long-cell {
display: flex;
align-items: flex-start;
}
.specialcondolence-info-card .specialcondolence-attachments .empty-container {
height: auto;
justify-content: flex-start;
color: inherit;
}
.specialcondolence-info-card .specialcondolence-attachments .empty-text {
font-size: inherit;
line-height: inherit;
}
.specialcondolence-info-opinion > :first-child {
margin-top: 0;
}
.specialcondolence-info-opinion > :last-child {
margin-bottom: 0;
}
.specialcondolence-signature-image,
.specialcondolence-approval-signature-image {
display: block;
width: 100%;
max-width: 100%;
height: 80px;
}
.specialcondolence-signature-image .van-image__img,
.specialcondolence-approval-signature-image {
object-fit: contain;
object-position: left center;
}
.specialcondolence-info-opinion img {
max-width: 100%;
height: auto;
}
@@ -0,0 +1,193 @@
const specialcondolenceInfo = {
template: /*language=HTML*/ `
<van-popup
v-model="visible"
position="right"
class="specialcondolence-full-popup"
:close-on-click-overlay="false"
get-container="#app"
>
<div class="specialcondolence-full-popup__page">
<van-nav-bar
:title="popupTitle"
left-arrow
left-text="返回"
fixed
@click-left="onClose()"
></van-nav-bar>
<div class="specialcondolence-full-popup__scroll">
<main class="specialcondolence-info-main">
<section class="specialcondolence-info-card">
<h2 class="specialcondolence-info-card__title">基本信息</h2>
<van-cell-group :border="false">
<van-cell title="申请人姓名">{{ viewData.applyUserName || '--' }}</van-cell>
<van-cell title="申请人工号">{{ viewData.applyLoginName || '--' }}</van-cell>
<van-cell title="申请人单位">{{ viewData.applyUnitName || '--' }}</van-cell>
<van-cell title="申请人工会">{{ viewData.applyUnionName || '--' }}</van-cell>
<template v-if="viewData.recipients && viewData.recipients.length">
<van-cell title="慰问类型" :value="viewData.typeName"></van-cell>
<van-cell title="每人慰问金额" :value="viewData.perMoney"></van-cell>
<van-cell title="慰问时间" :value="viewData.occurTime"></van-cell>
</template>
<template v-for="(person, index) in viewData.recipients">
<van-cell :title="'慰问对象 ' + (index + 1)" :value="person.helpUserName"></van-cell>
<van-cell title="工号" :value="person.helpLoginName || '--'"></van-cell>
<van-cell title="身份证号码" :value="person.idCard"></van-cell>
<van-cell title="开户行" :value="person.bankOfDeposit"></van-cell>
<van-cell title="户名" :value="person.bankUserName"></van-cell>
<van-cell title="收款账户" :value="person.bankCardNumber"></van-cell>
</template>
<template v-if="!viewData.recipients || !viewData.recipients.length">
<van-cell title="慰问对象">{{ viewData.helpUserName || '--' }}</van-cell>
<!-- <van-cell title="证明人">{{ viewData.certifierUserName || '--' }}</van-cell> -->
<van-cell title="慰问类型">{{ viewData.typeName || '--' }}</van-cell>
<van-cell title="慰问金额">{{ viewData.money || '--' }}</van-cell>
<van-cell title="慰问时间">{{ viewData.occurTime || '--' }}</van-cell>
<van-cell title="收款账户">{{ viewData.bankCardNumber || '--' }}</van-cell>
<van-cell title="户名">{{ viewData.bankUserName || '--' }}</van-cell>
<van-cell title="开户行">
{{ viewData.bankOfDeposit || '暂无' }}
</van-cell>
<template v-if="['2'].includes(viewData.typeCode)">
<van-cell title="入院时间">{{ viewData.hospitalizationTime || '--' }}</van-cell>
<van-cell title="出院时间">{{ viewData.leaveHospitalTime || '--' }}</van-cell>
<van-cell title="当年第几次住院(次)">{{ viewData.thisYearHospitalizationNum || '--' }}</van-cell>
</template>
<van-cell title="申请事由">
{{ viewData.remark || '暂无' }}
</van-cell>
</template>
<van-cell class="specialcondolence-info-long-cell" title="附件">
<file-preview class="specialcondolence-attachments" :files="viewData.files" complete_result></file-preview>
</van-cell>
<van-cell class="specialcondolence-info-long-cell" title="签字">
<van-image
v-if="viewData.signature"
:src="viewData.signature"
class="specialcondolence-signature-image"
fit="contain"
></van-image>
<span v-else>暂无签字</span>
</van-cell>
</van-cell-group>
</section>
<section
v-for="(task, index) in doneTasks"
:key="task.id || index"
class="specialcondolence-info-card"
>
<h2 class="specialcondolence-info-card__title">{{ task.displayName || '办理记录' }}</h2>
<van-cell-group :border="false" v-if="task.ext && task.ext.isFirstTaskNode">
<van-cell title="申请用户">
{{ task.ext.initiatorName || '--' }}({{ task.ext.initiatorAccount || '--' }})
</van-cell>
<van-cell title="申请时间">{{ task.finishTime || '--' }}</van-cell>
<van-cell title="办理结果">
<dict-tag
:options="dict.type.PROCESS_TASK_SUBMIT_TYPE"
:value="task.ext.submitType"
></dict-tag>
</van-cell>
</van-cell-group>
<van-cell-group :border="false" v-else>
<van-cell title="办理用户">
{{ task.taskFormData && task.taskFormData.userName || '--' }}({{ task.taskFormData && task.taskFormData.loginName || '--' }})
</van-cell>
<van-cell title="办理时间">{{ task.finishTime || '--' }}</van-cell>
<van-cell title="办理结果">
<dict-tag
:options="dict.type.PROCESS_TASK_SUBMIT_TYPE"
:value="task.ext && task.ext.submitType"
></dict-tag>
</van-cell>
<van-cell v-if="task.taskFormData && task.taskFormData.sign" title="审批签字">
<img :src="task.taskFormData.sign" class="specialcondolence-approval-signature-image">
</van-cell>
<van-cell title="办理意见">
<div class="specialcondolence-info-opinion" v-html="task.taskFormData && task.taskFormData.tf_opinion || '暂无'"></div>
</van-cell>
</van-cell-group>
</section>
<slot></slot>
</main>
</div>
</div>
</van-popup>
`,
dicts: ["PROCESS_TASK_SUBMIT_TYPE"],
props: {
pageKey: {
type: String,
required: true
}
},
data() {
return {
visible: false,
popupTitle: "慰问详情",
viewData: {},
doneTasks: [],
row: null,
historyLayerUnsubscribe: null
}
},
methods: {
// 浏览器历史变化时,只同步当前慰问详情弹框的显示状态。
syncHistoryLayerStack(stack) {
this.$set(this, "visible", stack.includes("specialcondolence-detail"))
},
/**
* 打开慰问详情或审核弹框。
* row 传列表当前行,title 传顶部导航标题;无返回值。
*/
onOpen(row, title = "慰问详情") {
this.$set(this, "row", row)
this.$set(this, "popupTitle", title)
this.$set(this, "viewData", {})
this.$set(this, "doneTasks", [])
this.getInfo()
this.getDoneTasks()
if (window.h5HistoryLayerManager && window.h5HistoryLayerManager.open("specialcondolence-detail")) return
this.$set(this, "visible", true)
},
// 顶部返回按钮与业务主动关闭统一回退弹框对应的浏览器历史。
onClose(afterClose = null) {
if (window.h5HistoryLayerManager && window.h5HistoryLayerManager.close("specialcondolence-detail", afterClose)) return
this.$set(this, "visible", false)
if (typeof afterClose === "function") afterClose()
},
getInfo() {
this.$axios.post("/platform/special_condolence/mine/info", {id: this.row.id}).then((res) => {
if (res.code === 0) {
this.$set(this, "viewData", res.data || {})
}
})
},
getDoneTasks() {
this.$axios.post("/flow/common/doneTasks", {bizId: this.row.id}).then((res) => {
if (res.code === 0) {
this.$set(this, "doneTasks", res.data || [])
}
})
}
},
created() {
if (!window.h5HistoryLayerManager) return
window.h5HistoryLayerManager.ensureRegistered(this.pageKey)
this.$set(this, "historyLayerUnsubscribe", window.h5HistoryLayerManager.subscribe((stack) => {
this.syncHistoryLayerStack(stack)
}))
},
beforeDestroy() {
if (typeof this.historyLayerUnsubscribe === "function") this.historyLayerUnsubscribe()
if (window.h5HistoryLayerManager && window.h5HistoryLayerManager.pageKey === this.pageKey) {
window.h5HistoryLayerManager.unregister(this.pageKey)
}
}
}
@@ -0,0 +1,367 @@
<!--#
layout("/layouts/platform_h5.html"){
#-->
<style>
#app { --color-primary: #1989fa; }
.specialcondolence-list-page { min-height: 100vh; padding-bottom: calc(24px + env(safe-area-inset-bottom)); box-sizing: border-box; background: #f3f7fd; }
.specialcondolence-list-page .van-nav-bar, .specialcondolence-list-page .van-sticky--fixed { background: #fff; }
.specialcondolence-list-page .van-nav-bar__title { color: #172033; font-size: 16px; font-weight: 600; }
.specialcondolence-list-page .van-nav-bar .van-icon, .specialcondolence-list-page .van-nav-bar__text { color: #1989fa; }
.specialcondolence-list-sticky { padding: 22px 12px 1px; box-sizing: border-box; background: #f3f7fd; }
.specialcondolence-list-search { margin: 0 0 8px; padding: 0; overflow: hidden; border-radius: 12px; background: #fff; box-shadow: 0 5px 16px rgba(43, 73, 112, .1); }
.specialcondolence-list-search .van-search__content { height: 42px; padding-left: 12px; align-items: center; border-radius: 12px; background: #fff; }
.specialcondolence-list-search .van-field__control { color: #263548; font-size: 14px; }
.specialcondolence-list-search .van-field__control::placeholder { color: #a7b0bf; }
.specialcondolence-list-filter { margin-bottom: 8px; overflow: hidden; border-radius: 10px; box-shadow: 0 5px 16px rgba(43, 73, 112, .08); }
.specialcondolence-list-filter .van-dropdown-menu__bar { height: 44px; box-shadow: none; }
.specialcondolence-list-filter .van-dropdown-menu__item, .specialcondolence-list-filter .van-dropdown-menu__title { height: 44px; }
.specialcondolence-list-filter .van-dropdown-menu__item { align-items: center; justify-content: center; }
.specialcondolence-list-filter .van-dropdown-menu__title { display: inline-flex; align-items: center; line-height: 20px; color: #66758a; font-size: 14px; }
.specialcondolence-list-filter .van-dropdown-menu__title--active, .specialcondolence-list-filter .van-dropdown-item__option--active, .specialcondolence-list-filter .van-dropdown-item__option--active .van-dropdown-item__icon { color: #1989fa; }
.specialcondolence-list-filter .van-dropdown-item__content { width: calc(100% - 24px); margin: 0 12px; overflow: hidden; box-sizing: border-box; border-radius: 0 0 12px 12px; box-shadow: 0 10px 24px rgba(31, 65, 108, .14); }
.specialcondolence-list-filter .van-dropdown-item__option { display: flex; min-height: 48px; padding: 0 16px; align-items: center; color: #46566c; font-size: 14px; }
.specialcondolence-list-filter .van-dropdown-item__option .van-cell__title { display: flex; min-height: 20px; align-items: center; line-height: 20px; }
.specialcondolence-list-content { padding: 0 12px; }
.specialcondolence-list-skeleton { padding-top: 2px; }
.specialcondolence-list-skeleton__card { margin-bottom: 12px; padding: 18px 14px 14px; border: 1px solid #e9eff7; border-radius: 12px; background: #fff; box-shadow: 0 5px 16px rgba(43, 73, 112, .08); }
.specialcondolence-list-skeleton__card .van-skeleton { padding: 0; }
.specialcondolence-list-skeleton__card .van-skeleton__title { width: 42%; height: 18px; margin-bottom: 18px; }
.specialcondolence-list-skeleton__card .van-skeleton__row { height: 14px; margin-top: 12px; }
.specialcondolence-list-skeleton__card .van-skeleton__row:nth-child(2n) { width: 72% !important; }
.specialcondolence-list-skeleton__card .van-skeleton__row:last-child { width: 34% !important; margin-top: 18px; margin-left: auto; }
.specialcondolence-list-page .table-list-container { margin-top: 0; padding-bottom: 2px; }
.specialcondolence-list-page .table-list-container .table-list-item { margin-bottom: 12px; padding: 14px; border: 1px solid #e9eff7; border-radius: 12px; box-shadow: 0 5px 16px rgba(43, 73, 112, .08); }
.specialcondolence-card-header { display: flex; margin-bottom: 8px; align-items: flex-start; justify-content: space-between; }
.specialcondolence-card-heading { min-width: 0; padding-right: 10px; flex: 1; }
.specialcondolence-card-title { overflow: hidden; color: #253247; font-size: 15px; font-weight: 600; line-height: 22px; text-overflow: ellipsis; white-space: nowrap; }
.specialcondolence-card-code { margin-top: 1px; color: #9ba6b6; font-size: 12px; line-height: 18px; }
.specialcondolence-card-status { display: inline-flex; flex: none; }
.specialcondolence-card-status .van-tag { min-height: 24px; padding: 3px 9px; border: 0; border-radius: 12px; box-sizing: border-box; font-size: 12px; line-height: 18px; }
.specialcondolence-list-page .table-list-container .table-list-item .item-actions { margin-top: 10px; padding-top: 10px; border-top-color: #edf1f6; }
.specialcondolence-list-page .table-list-container .table-list-item .action-btn { min-height: 28px; padding: 4px 11px; border-radius: 14px; color: #1989fa; background: #eaf4ff; font-size: 12px; line-height: 18px; }
.specialcondolence-list-page .table-list-container .table-list-item .action-btn.delete { color: #f04444; background: #fff0f0; }
.specialcondolence-list-page .table-list-container .table-list-item .action-btn i { margin-right: 5px; font-size: 13px; }
.specialcondolence-list-page .empty-state { padding: 0; }
.specialcondolence-list-empty { display: flex; min-height: 280px; padding: 46px 18px 36px; align-items: center; justify-content: center; box-sizing: border-box; flex-direction: column; text-align: center; }
.specialcondolence-list-empty img { display: block; width: 86%; max-width: 290px; height: auto; object-fit: contain; }
.specialcondolence-list-empty__title { margin-top: 14px; color: #50627a; font-size: 15px; font-weight: 600; line-height: 22px; }
.specialcondolence-list-empty__hint { margin-top: 5px; color: #9aa7b8; font-size: 12px; line-height: 18px; }
</style>
<style id="style-specialcondolence-info-h5">
<!--#include("../common/info.css"){}#-->
</style>
<div id="app">
<div class="specialcondolence-list-page">
<van-nav-bar title="分管校领导审批" left-text="返回" left-arrow @click-left="historyBack" fixed placeholder></van-nav-bar>
<van-sticky offset-top="46px">
<div class="specialcondolence-list-sticky">
<van-search
v-model="pageForm.searchKeyword"
class="specialcondolence-list-search"
placeholder="请输入姓名或者工号搜索"
shape="round"
clearable
@search="doSearch"
@clear="doSearch"
></van-search>
<van-dropdown-menu class="specialcondolence-list-filter" :close-on-click-outside="false" :close-on-click-overlay="false">
<year-van-dropdown-item :num="5" @change="doSearch" v-model="pageForm.year"></year-van-dropdown-item>
<van-dropdown-item v-model="pageForm.type" :options="typeOptions" :multiple="false"
@change="doSearch"></van-dropdown-item>
<van-dropdown-item v-model="pageForm.approvalText" :options="approvalOptions" :multiple="false"
@change="onApprovalFilterChange"></van-dropdown-item>
</van-dropdown-menu>
</div>
</van-sticky>
<main class="specialcondolence-list-content">
<div class="specialcondolence-list-skeleton" v-if="skeletonLoading">
<div class="specialcondolence-list-skeleton__card" v-for="index in 3" :key="'fourth-approval-skeleton-' + index">
<van-skeleton title :row="5" animated></van-skeleton>
</div>
</div>
<table-list api="/platform/special_condolence/fourthApproval/pageData" :page_form.sync="pageForm" ref="tableListRef"
v-show="!skeletonLoading" :show_loading_overlay="false" @ready="onReady"
@loading-change="handleListLoadingChange">
<template #header="{index,row}">
<div class="specialcondolence-card-header">
<div class="specialcondolence-card-heading">
<div class="specialcondolence-card-title">{{row.helpUserName || '--'}}</div>
<div class="specialcondolence-card-code">工号:{{row.helpLoginName || '--'}}</div>
</div>
<enum-tag class="specialcondolence-card-status" v-if="row.instanceState" :value="row.instanceState"
name="ProcessInstanceStateEnum" label_key="message" size="small"></enum-tag>
</div>
</template>
<template v-slot="{index,row}">
<table-column label="慰问对象">{{row.helpUserName}}</table-column>
<table-column label="慰问对象工号">{{row.helpLoginName}}</table-column>
<table-column label="申请人">{{row.applyUserName}}</table-column>
<table-column label="所属工会">{{row.applyUnionName}}</table-column>
<table-column label="所属单位">{{row.applyUnitName}}</table-column>
<table-column label="慰问类型">{{row.typeName}}</table-column>
<table-column label="当前节点">{{row.curTaskName}}</table-column>
</template>
<template #actions="{index,row}">
<div class="action-btn" @click="onView(row)">
<van-icon name="eye-o"></van-icon>
<span>查看</span>
</div>
<div class="action-btn" v-if="row.taskState === 10" @click="onApproval(row)">
<van-icon name="edit"></van-icon>
<span>审核</span>
</div>
<div class="action-btn delete" v-if="row.canRevoke" @click="onRevoke(row)">
<van-icon name="revoke"></van-icon>
<span>撤回</span>
</div>
</template>
<template #empty>
<div class="specialcondolence-list-empty">
<img src="/assets/mobile/img/condolence/condolence-empty.png" alt="暂无慰问审核记录插画">
<div class="specialcondolence-list-empty__title">暂无慰问审核记录</div>
<div class="specialcondolence-list-empty__hint">当前筛选条件下暂时没有慰问申请</div>
</div>
</template>
</table-list>
</main>
<info ref="infoRef" page-key="specialcondolence-fourth-detail">
<div v-if="showApprovalForm">
<section class="specialcondolence-info-card">
<h2 class="specialcondolence-info-card__title">
审批意见<span class="specialcondolence-audit-card__required">*</span>
</h2>
<van-form ref="formRef">
<van-field
v-model="formData.tf_opinion"
class="specialcondolence-audit-field"
name="tf_opinion"
type="textarea"
rows="6"
placeholder="请输入审批意见"
:rules="[{ required: true, message: '请填写审批意见' }]"
maxlength="100"
show-word-limit
></van-field>
<h2 class="specialcondolence-info-card__title">
审批签字<span class="specialcondolence-audit-card__required">*</span>
</h2>
<van-field name="tf_sign" :value="formData.tf_sign" :rules="[{required:true,message:'请完成审批签字'}]">
<template #input><h5-signature v-model="formData.tf_sign" :key="formData.processTaskId"></h5-signature></template>
</van-field>
</van-form>
</section>
<div class="specialcondolence-popup-actions">
<van-button class="specialcondolence-action-danger" type="danger" block :loading="formLoading" @click="handleTaskAction(6)">退回</van-button>
<van-button class="specialcondolence-action-danger" type="danger" block :loading="formLoading" @click="handleTaskAction(2)">不同意</van-button>
<van-button class="specialcondolence-action-primary" type="primary" block :loading="formLoading" @click="handleTaskAction(1)">同意</van-button>
</div>
</div>
</info>
<van-dialog
:value="confirmVisible"
title="提示"
:message="confirmActionType === 'revoke' ? '您确定要撤回吗?' : '您确定要提交吗?'"
show-cancel-button
confirm-button-color="#1989fa"
@confirm="confirmPendingAction"
@cancel="closeActionConfirm"
></van-dialog>
</div>
</div>
<script nonce="${cspNonce!}">
<!--#include('../common/info.js'){}#-->
new Vue({
el: "#app",
store,
components: {
info: specialcondolenceInfo
},
data() {
return {
pageForm: {
pageNumber: 1,
pageSize: 10,
totalCount: 0,
searchKeyword: '',
approvalText: "0",
approval: false,
year: new Date().getFullYear(),
type: null,
},
typeOptions: [],
approvalOptions: [
{text: "未审核", value: "0"},
{text: "已审核", value: "1"}
],
skeletonLoading: true,
formData: {},
showApprovalForm: false,
formLoading: false,
confirmVisible: false,
confirmActionType: "task",
pendingSubmitType: null,
pendingRevokeRow: null,
historyLayerUnsubscribe: null
}
},
methods: {
// state.initial 表示当前查询前列表为空,仅此阶段使用整组卡片骨架屏。
handleListLoadingChange(state) {
if (state.initial) this.$set(this, "skeletonLoading", state.loading)
},
// 审核状态下拉值为字符串,接口参数 approval 继续使用布尔值。
onApprovalFilterChange(val) {
this.$set(this.pageForm, "approval", val === "1")
this.doSearch()
},
onReady() {
this.querySpecialCondolenceType()
this.openTodoTask()
},
openTodoTask() {
const taskId = GetQueryString("taskId")
const bizId = GetQueryString("bizId")
if (!taskId || !bizId) return
this.$axios.post("/flow/common/taskInfo", {taskId}).then((res) => {
if (res.code !== 0 || !res.data) return
const task = res.data
if (task.taskName !== "af363a4e-fc56-42a0-9906-8c7a2814006b") return
const row = {id: bizId, taskId: task.id, curTaskName: task.displayName}
if (task.taskState === 10 && task.executable && GetQueryString("tab") === "todo") {
this.onApproval(row)
} else {
this.onView(row)
}
})
},
onView(row) {
this.$set(this, "showApprovalForm", false)
this.$refs.infoRef.onOpen(row, "慰问详情")
},
onApproval(row) {
this.$set(this, "showApprovalForm", true)
this.$set(this, "formData", {
processTaskId: row.taskId,
taskName: row.curTaskName,
tf_sign: "",
tf_opinion: ""
})
this.$refs.infoRef.onOpen(row, row.curTaskName || "慰问审核")
},
// val 为流程提交类型:1 同意、20 不同意、6 退回;校验通过后打开历史栈确认框。
handleTaskAction(val) {
this.$refs.formRef.validate()
.then(() => {
this.$set(this, "confirmActionType", "task")
this.$set(this, "pendingSubmitType", val)
if (window.h5HistoryLayerManager && window.h5HistoryLayerManager.open("specialcondolence-action-confirm")) return
this.$set(this, "confirmVisible", true)
})
.catch(() => {})
},
// 确认操作时先回退确认框历史,再根据操作类型调用办理或撤回接口。
confirmPendingAction() {
this.closeActionConfirm(() => {
if (this.confirmActionType === "revoke") {
this.revokeTask()
return
}
this.submitTaskAction()
})
},
// 提交 processTaskId、taskName、tf_opinion、tf_sign 和 submitType,接口返回统一响应对象。
submitTaskAction() {
this.$set(this, "formLoading", true)
this.$axios.post("/platform/special_condolence/approval/executeTask", {
data: JSON.stringify({
...this.formData,
submitType: this.pendingSubmitType
})
})
.then((res) => {
if (res && res.code === 0) {
const afterClose = () => {
this.$toast.success(res.msg)
this.doSearch()
}
if (window.h5HistoryLayerManager && window.h5HistoryLayerManager.closeAll(afterClose)) return
this.$refs.infoRef.onClose(afterClose)
}
})
.finally(() => {
this.$set(this, "formLoading", false)
})
},
closeActionConfirm(afterClose = null) {
if (window.h5HistoryLayerManager && window.h5HistoryLayerManager.close("specialcondolence-action-confirm", afterClose)) return
this.$set(this, "confirmVisible", false)
if (typeof afterClose === "function") afterClose()
},
// row 提供待撤回任务 taskId,确认后保存当前行并由统一确认框执行撤回。
onRevoke(row){
this.$set(this, "confirmActionType", "revoke")
this.$set(this, "pendingRevokeRow", row)
if (window.h5HistoryLayerManager && window.h5HistoryLayerManager.open("specialcondolence-action-confirm")) return
this.$set(this, "confirmVisible", true)
},
// 使用 pendingRevokeRow.taskId 撤回任务,接口返回统一响应对象。
revokeTask() {
if (!this.pendingRevokeRow) return
this.$set(this, "formLoading", true)
this.$axios.post("/flow/common/revokeTask", {taskId: this.pendingRevokeRow.taskId})
.then((res) => {
if (res.code === 0) {
this.$toast.success(res.msg)
this.doSearch()
}
})
.finally(() => {
this.$set(this, "formLoading", false)
})
},
querySpecialCondolenceType() {
this.$axios.post('/platform/special_condolence/type/querySpecialCondolenceType', {}, {h5SkeletonLoading: true})
.then((res) => {
const typeList = res.data || []
this.$set(this, "typeOptions", [
{
text: "全部慰问类型",
value: null
}
].concat(typeList.map((v) => ({ text: v.name, value: v.id }))))
if (this.typeOptions.length > 0) {
this.$set(this.pageForm, "type", this.typeOptions[0].value)
this.doSearch()
}
})
},
doSearch() {
this.$nextTick(() => {
this.pageForm.pageNumber = 1
this.pageForm.totalCount = 0
this.$refs.tableListRef.doSearch()
})
}
},
created() {
if (!window.h5HistoryLayerManager) return
this.$set(this, "historyLayerUnsubscribe", window.h5HistoryLayerManager.subscribe((stack) => {
this.$set(this, "confirmVisible", stack.includes("specialcondolence-action-confirm"))
}))
},
beforeDestroy() {
if (typeof this.historyLayerUnsubscribe === "function") this.historyLayerUnsubscribe()
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,475 @@
<!--#
layout("/layouts/platform_h5.html"){
#-->
<style>
#app {
--color-primary: #1989fa;
}
.specialcondolence-list-page {
min-height: 100vh;
padding-bottom: calc(24px + env(safe-area-inset-bottom));
box-sizing: border-box;
background: #f3f7fd;
}
.specialcondolence-list-page .van-nav-bar,
.specialcondolence-list-page .van-sticky--fixed {
background: #fff;
}
.specialcondolence-list-page .van-nav-bar__title {
color: #172033;
font-size: 16px;
font-weight: 600;
}
.specialcondolence-list-page .van-nav-bar .van-icon,
.specialcondolence-list-page .van-nav-bar__text {
color: #1989fa;
}
.specialcondolence-list-sticky {
padding: 22px 12px 1px;
box-sizing: border-box;
background: #f3f7fd;
}
.specialcondolence-list-search {
margin: 0 0 8px;
padding: 0;
overflow: hidden;
border-radius: 12px;
background: #fff;
box-shadow: 0 5px 16px rgba(43, 73, 112, .1);
}
.specialcondolence-list-search .van-search__content {
height: 42px;
padding-left: 12px;
align-items: center;
border-radius: 12px;
background: #fff;
}
.specialcondolence-list-search .van-field__control {
color: #263548;
font-size: 14px;
}
.specialcondolence-list-search .van-field__control::placeholder {
color: #a7b0bf;
}
.specialcondolence-list-filter {
margin-bottom: 10px;
overflow: hidden;
border-radius: 10px;
box-shadow: 0 5px 16px rgba(43, 73, 112, .08);
}
.specialcondolence-list-filter .van-dropdown-menu__bar {
height: 44px;
box-shadow: none;
}
.specialcondolence-list-filter .van-dropdown-menu__item,
.specialcondolence-list-filter .van-dropdown-menu__title {
height: 44px;
}
.specialcondolence-list-filter .van-dropdown-menu__item {
align-items: center;
justify-content: center;
}
.specialcondolence-list-filter .van-dropdown-menu__title {
display: inline-flex;
align-items: center;
line-height: 20px;
color: #66758a;
font-size: 14px;
}
.specialcondolence-list-filter .van-dropdown-menu__title--active,
.specialcondolence-list-filter .van-dropdown-item__option--active,
.specialcondolence-list-filter .van-dropdown-item__option--active .van-dropdown-item__icon {
color: #1989fa;
}
.specialcondolence-list-filter .van-dropdown-item__content {
width: calc(100% - 24px);
margin: 0 12px;
overflow: hidden;
box-sizing: border-box;
border-radius: 0 0 12px 12px;
box-shadow: 0 10px 24px rgba(31, 65, 108, .14);
}
.specialcondolence-list-filter .van-dropdown-item__option {
display: flex;
min-height: 48px;
padding: 0 16px;
align-items: center;
color: #46566c;
font-size: 14px;
}
.specialcondolence-list-filter .van-dropdown-item__option .van-cell__title {
display: flex;
min-height: 20px;
align-items: center;
line-height: 20px;
}
.specialcondolence-list-content {
padding: 0 12px;
}
.specialcondolence-list-skeleton {
padding-top: 2px;
}
.specialcondolence-list-skeleton__card {
margin-bottom: 12px;
padding: 18px 14px 14px;
border: 1px solid #e9eff7;
border-radius: 12px;
background: #fff;
box-shadow: 0 5px 16px rgba(43, 73, 112, .08);
}
.specialcondolence-list-skeleton__card .van-skeleton {
padding: 0;
}
.specialcondolence-list-skeleton__card .van-skeleton__title {
width: 42%;
height: 18px;
margin-bottom: 18px;
}
.specialcondolence-list-skeleton__card .van-skeleton__row {
height: 14px;
margin-top: 12px;
}
.specialcondolence-list-skeleton__card .van-skeleton__row:nth-child(2n) {
width: 72% !important;
}
.specialcondolence-list-skeleton__card .van-skeleton__row:last-child {
width: 34% !important;
margin-top: 18px;
margin-left: auto;
}
.specialcondolence-list-page .table-list-container {
margin-top: 0;
padding-bottom: 2px;
}
.specialcondolence-list-page .table-list-container .table-list-item {
margin-bottom: 12px;
padding: 14px;
border: 1px solid #e9eff7;
border-radius: 12px;
box-shadow: 0 5px 16px rgba(43, 73, 112, .08);
}
.specialcondolence-card-header {
display: flex;
margin-bottom: 8px;
align-items: flex-start;
justify-content: space-between;
}
.specialcondolence-card-heading {
min-width: 0;
padding-right: 10px;
flex: 1;
}
.specialcondolence-card-title {
overflow: hidden;
color: #253247;
font-size: 15px;
font-weight: 600;
line-height: 22px;
text-overflow: ellipsis;
white-space: nowrap;
}
.specialcondolence-card-code {
margin-top: 1px;
color: #9ba6b6;
font-size: 12px;
line-height: 18px;
}
.specialcondolence-card-status {
display: inline-flex;
flex: none;
}
.specialcondolence-card-status .van-tag {
min-height: 24px;
padding: 3px 9px;
border: 0;
border-radius: 12px;
box-sizing: border-box;
font-size: 12px;
line-height: 18px;
}
.specialcondolence-list-page .table-list-container .table-list-item .item-actions {
margin-top: 10px;
padding-top: 10px;
border-top-color: #edf1f6;
}
.specialcondolence-list-page .table-list-container .table-list-item .action-btn {
min-height: 28px;
padding: 4px 11px;
border-radius: 14px;
color: #1989fa;
background: #eaf4ff;
font-size: 12px;
line-height: 18px;
}
.specialcondolence-list-page .table-list-container .table-list-item .action-btn.delete {
color: #f04444;
background: #fff0f0;
}
.specialcondolence-list-page .table-list-container .table-list-item .action-btn i {
margin-right: 5px;
font-size: 13px;
}
.specialcondolence-list-page .empty-state {
padding: 0;
}
.specialcondolence-list-empty {
display: flex;
min-height: 280px;
padding: 46px 18px 36px;
align-items: center;
justify-content: center;
box-sizing: border-box;
flex-direction: column;
text-align: center;
}
.specialcondolence-list-empty img {
display: block;
width: 86%;
max-width: 290px;
height: auto;
object-fit: contain;
}
.specialcondolence-list-empty__title {
margin-top: 14px;
color: #50627a;
font-size: 15px;
font-weight: 600;
line-height: 22px;
}
.specialcondolence-list-empty__hint {
margin-top: 5px;
color: #9aa7b8;
font-size: 12px;
line-height: 18px;
}
</style>
<style id="style-specialcondolence-info-h5">
<!--#include("../common/info.css"){}#-->
</style>
<div id="app">
<div class="specialcondolence-list-page">
<van-nav-bar title="我的申请" left-text="返回" left-arrow @click-left="historyBack" fixed placeholder></van-nav-bar>
<van-sticky offset-top="46px">
<div class="specialcondolence-list-sticky">
<van-search
v-model="pageForm.searchKeyword"
class="specialcondolence-list-search"
placeholder="请输入姓名或者工号搜索"
shape="round"
clearable
@search="doSearch"
@clear="doSearch"
></van-search>
<van-dropdown-menu class="specialcondolence-list-filter" :close-on-click-outside="false" :close-on-click-overlay="false">
<year-van-dropdown-item :num="5" @change="doSearch" v-model="pageForm.year"></year-van-dropdown-item>
<van-dropdown-item v-model="pageForm.type" :options="typeOptions" :multiple="false"
@change="doSearch"></van-dropdown-item>
</van-dropdown-menu>
</div>
</van-sticky>
<main class="specialcondolence-list-content">
<div class="specialcondolence-list-skeleton" v-if="skeletonLoading">
<div class="specialcondolence-list-skeleton__card" v-for="index in 3" :key="'mine-skeleton-' + index">
<van-skeleton title :row="5" animated></van-skeleton>
</div>
</div>
<table-list api="/platform/special_condolence/mine/pageData" :page_form.sync="pageForm" ref="tableListRef"
v-show="!skeletonLoading" :show_loading_overlay="false" @ready="onReady"
@loading-change="handleListLoadingChange">
<template #header="{index,row}">
<div class="specialcondolence-card-header">
<div class="specialcondolence-card-heading">
<div class="specialcondolence-card-title">{{row.helpUserName || '--'}}</div>
<div class="specialcondolence-card-code">工号:{{row.helpLoginName || '--'}}</div>
</div>
<enum-tag class="specialcondolence-card-status" v-if="row.instanceState" :value="row.instanceState"
name="ProcessInstanceStateEnum" label_key="message" size="small"></enum-tag>
</div>
</template>
<template v-slot="{index,row}">
<table-column label="慰问对象">{{row.helpUserName}}</table-column>
<table-column label="慰问对象工号">{{row.helpLoginName}}</table-column>
<table-column label="申请人">{{row.applyUserName}}</table-column>
<table-column label="所属工会">{{row.applyUnionName}}</table-column>
<table-column label="所属单位">{{row.applyUnitName}}</table-column>
<table-column label="慰问类型">{{row.typeName}}</table-column>
<table-column label="当前节点">{{row.taskName}}</table-column>
</template>
<template #actions="{index,row}">
<div class="action-btn" @click="onView(row)">
<van-icon name="eye-o"></van-icon>
<span>查看</span>
</div>
<div class="action-btn" @click="onEdit(row)"
v-if="row.taskKey === 'startTask' || !row.instanceId">
<van-icon name="edit"></van-icon>
<span>编辑</span>
</div>
<div class="action-btn delete" @click="onRevoke(row)"
v-if="row.canRevoke">
<van-icon name="revoke"></van-icon>
<span>撤回</span>
</div>
<div class="action-btn delete" @click="onDelete(row)"
v-if="row.taskKey === 'startTask' || !row.instanceId">
<van-icon name="delete-o"></van-icon>
<span>删除</span>
</div>
</template>
<template #empty>
<div class="specialcondolence-list-empty">
<img src="/assets/mobile/img/condolence/condolence-empty.png" alt="暂无慰问申请记录插画">
<div class="specialcondolence-list-empty__title">暂无慰问申请记录</div>
<div class="specialcondolence-list-empty__hint">当前筛选条件下暂时没有慰问申请</div>
</div>
</template>
</table-list>
</main>
<info ref="infoRef" page-key="specialcondolence-mine-detail"></info>
</div>
</div>
<script nonce="${cspNonce!}">
<!--#include('../common/info.js'){}#-->
new Vue({
el: "#app",
store,
components: {
info: specialcondolenceInfo
},
data() {
return {
pageForm: {
pageNumber: 1,
pageSize: 10,
totalCount: 0,
year: new Date().getFullYear(),
type: null
},
typeOptions: [],
skeletonLoading: true,
}
},
methods: {
// state.initial 表示当前查询前列表为空,仅此阶段使用整组卡片骨架屏。
handleListLoadingChange(state) {
if (state.initial) this.$set(this, "skeletonLoading", state.loading)
},
onReady() {
this.querySpecialCondolenceType()
.then((typeList) => {
this.$set(this, "typeOptions", [
{
text: "全部慰问类型",
value: null
}
].concat(typeList.map((v) => ({ text: v.name, value: v.id }))))
if (this.typeOptions.length > 0) {
this.$set(this.pageForm, "type", this.typeOptions[0].value)
this.doSearch()
}
})
},
onRevoke(row) {
this.$dialog.confirm({
title: "提示",
message: "您确定要撤回吗?"
}).then(() => {
this.$axios.post("/flow/common/revokeTask", { taskId: row.startTaskId }).then((res) => {
if (res.code === 0) {
this.$toast(res.msg)
this.doSearch()
}
})
})
},
onView(row) {
this.$refs.infoRef.onOpen(row, "慰问详情")
},
onEdit(row) {
this.$pjaxReplace("/platform/special_condolence/apply/h5?taskId=" + (row.startTaskId || "") + "&bizId=" + row.id)
},
onDelete(row) {
this.$dialog.confirm({
title: "提示",
message: "您确定要删除吗?"
})
.then(() => {
this.$axios.post("/platform/special_condolence/mine/delete", { id: row.id }).then((res) => {
if (res.code === 0) {
this.$toast.success(res.msg)
this.doSearch()
} else {
this.$toast.fail(res.msg)
}
})
})
.catch(() => {})
},
querySpecialCondolenceType() {
return this.$axios.post('/platform/special_condolence/type/querySpecialCondolenceType', {}, {h5SkeletonLoading: true})
.then((res) => res.data || [])
},
doSearch() {
this.$nextTick(() => {
this.pageForm.pageNumber = 1
this.pageForm.totalCount = 0
this.$refs.tableListRef.doSearch()
})
}
},
})
</script>
<!--#
}
#-->
@@ -0,0 +1,350 @@
<!--#
layout("/layouts/platform_h5.html"){
#-->
<style>
#app { --color-primary: #1989fa; }
.specialcondolence-list-page { min-height: 100vh; padding-bottom: calc(24px + env(safe-area-inset-bottom)); box-sizing: border-box; background: #f3f7fd; }
.specialcondolence-list-page .van-nav-bar, .specialcondolence-list-page .van-sticky--fixed { background: #fff; }
.specialcondolence-list-page .van-nav-bar__title { color: #172033; font-size: 16px; font-weight: 600; }
.specialcondolence-list-page .van-nav-bar .van-icon, .specialcondolence-list-page .van-nav-bar__text { color: #1989fa; }
.specialcondolence-list-sticky { padding: 22px 12px 1px; box-sizing: border-box; background: #f3f7fd; }
.specialcondolence-list-search { margin: 0 0 8px; padding: 0; overflow: hidden; border-radius: 12px; background: #fff; box-shadow: 0 5px 16px rgba(43, 73, 112, .1); }
.specialcondolence-list-search .van-search__content { height: 42px; padding-left: 12px; align-items: center; border-radius: 12px; background: #fff; }
.specialcondolence-list-search .van-field__control { color: #263548; font-size: 14px; }
.specialcondolence-list-search .van-field__control::placeholder { color: #a7b0bf; }
.specialcondolence-list-filter { margin-bottom: 8px; overflow: hidden; border-radius: 10px; box-shadow: 0 5px 16px rgba(43, 73, 112, .08); }
.specialcondolence-list-filter .van-dropdown-menu__bar { height: 44px; box-shadow: none; }
.specialcondolence-list-filter .van-dropdown-menu__item, .specialcondolence-list-filter .van-dropdown-menu__title { height: 44px; }
.specialcondolence-list-filter .van-dropdown-menu__item { align-items: center; justify-content: center; }
.specialcondolence-list-filter .van-dropdown-menu__title { display: inline-flex; align-items: center; line-height: 20px; color: #66758a; font-size: 14px; }
.specialcondolence-list-filter .van-dropdown-menu__title--active, .specialcondolence-list-filter .van-dropdown-item__option--active, .specialcondolence-list-filter .van-dropdown-item__option--active .van-dropdown-item__icon { color: #1989fa; }
.specialcondolence-list-filter .van-dropdown-item__content { width: calc(100% - 24px); margin: 0 12px; overflow: hidden; box-sizing: border-box; border-radius: 0 0 12px 12px; box-shadow: 0 10px 24px rgba(31, 65, 108, .14); }
.specialcondolence-list-filter .van-dropdown-item__option { display: flex; min-height: 48px; padding: 0 16px; align-items: center; color: #46566c; font-size: 14px; }
.specialcondolence-list-filter .van-dropdown-item__option .van-cell__title { display: flex; min-height: 20px; align-items: center; line-height: 20px; }
.specialcondolence-list-content { padding: 0 12px; }
.specialcondolence-list-skeleton { padding-top: 2px; }
.specialcondolence-list-skeleton__card { margin-bottom: 12px; padding: 18px 14px 14px; border: 1px solid #e9eff7; border-radius: 12px; background: #fff; box-shadow: 0 5px 16px rgba(43, 73, 112, .08); }
.specialcondolence-list-skeleton__card .van-skeleton { padding: 0; }
.specialcondolence-list-skeleton__card .van-skeleton__title { width: 42%; height: 18px; margin-bottom: 18px; }
.specialcondolence-list-skeleton__card .van-skeleton__row { height: 14px; margin-top: 12px; }
.specialcondolence-list-skeleton__card .van-skeleton__row:nth-child(2n) { width: 72% !important; }
.specialcondolence-list-skeleton__card .van-skeleton__row:last-child { width: 34% !important; margin-top: 18px; margin-left: auto; }
.specialcondolence-list-page .table-list-container { margin-top: 0; padding-bottom: 2px; }
.specialcondolence-list-page .table-list-container .table-list-item { margin-bottom: 12px; padding: 14px; border: 1px solid #e9eff7; border-radius: 12px; box-shadow: 0 5px 16px rgba(43, 73, 112, .08); }
.specialcondolence-card-header { display: flex; margin-bottom: 8px; align-items: flex-start; justify-content: space-between; }
.specialcondolence-card-heading { min-width: 0; padding-right: 10px; flex: 1; }
.specialcondolence-card-title { overflow: hidden; color: #253247; font-size: 15px; font-weight: 600; line-height: 22px; text-overflow: ellipsis; white-space: nowrap; }
.specialcondolence-card-code { margin-top: 1px; color: #9ba6b6; font-size: 12px; line-height: 18px; }
.specialcondolence-card-status { display: inline-flex; flex: none; }
.specialcondolence-card-status .van-tag { min-height: 24px; padding: 3px 9px; border: 0; border-radius: 12px; box-sizing: border-box; font-size: 12px; line-height: 18px; }
.specialcondolence-list-page .table-list-container .table-list-item .item-actions { margin-top: 10px; padding-top: 10px; border-top-color: #edf1f6; }
.specialcondolence-list-page .table-list-container .table-list-item .action-btn { min-height: 28px; padding: 4px 11px; border-radius: 14px; color: #1989fa; background: #eaf4ff; font-size: 12px; line-height: 18px; }
.specialcondolence-list-page .table-list-container .table-list-item .action-btn.delete { color: #f04444; background: #fff0f0; }
.specialcondolence-list-page .table-list-container .table-list-item .action-btn i { margin-right: 5px; font-size: 13px; }
.specialcondolence-list-page .empty-state { padding: 0; }
.specialcondolence-list-empty { display: flex; min-height: 280px; padding: 46px 18px 36px; align-items: center; justify-content: center; box-sizing: border-box; flex-direction: column; text-align: center; }
.specialcondolence-list-empty img { display: block; width: 86%; max-width: 290px; height: auto; object-fit: contain; }
.specialcondolence-list-empty__title { margin-top: 14px; color: #50627a; font-size: 15px; font-weight: 600; line-height: 22px; }
.specialcondolence-list-empty__hint { margin-top: 5px; color: #9aa7b8; font-size: 12px; line-height: 18px; }
</style>
<style id="style-specialcondolence-info-h5">
<!--#include("../common/info.css"){}#-->
</style>
<div id="app">
<div class="specialcondolence-list-page">
<van-nav-bar title="校工会办公室主任审批" left-text="返回" left-arrow @click-left="historyBack" fixed placeholder></van-nav-bar>
<van-sticky offset-top="46px">
<div class="specialcondolence-list-sticky">
<van-search
v-model="pageForm.searchKeyword"
class="specialcondolence-list-search"
placeholder="请输入姓名或者工号搜索"
shape="round"
clearable
@search="doSearch"
@clear="doSearch"
></van-search>
<van-dropdown-menu class="specialcondolence-list-filter" :close-on-click-outside="false" :close-on-click-overlay="false">
<year-van-dropdown-item :num="5" @change="doSearch" v-model="pageForm.year"></year-van-dropdown-item>
<van-dropdown-item v-model="pageForm.type" :options="typeOptions" :multiple="false"
@change="doSearch"></van-dropdown-item>
<van-dropdown-item v-model="pageForm.approvalText" :options="approvalOptions" :multiple="false"
@change="onApprovalFilterChange"></van-dropdown-item>
</van-dropdown-menu>
</div>
</van-sticky>
<main class="specialcondolence-list-content">
<div class="specialcondolence-list-skeleton" v-if="skeletonLoading">
<div class="specialcondolence-list-skeleton__card" v-for="index in 3" :key="'school-principal-approval-skeleton-' + index">
<van-skeleton title :row="5" animated></van-skeleton>
</div>
</div>
<table-list api="/platform/special_condolence/schoolPrincipalApproval/pageData" :page_form.sync="pageForm" ref="tableListRef"
v-show="!skeletonLoading" :show_loading_overlay="false" @ready="onReady"
@loading-change="handleListLoadingChange">
<template #header="{index,row}">
<div class="specialcondolence-card-header">
<div class="specialcondolence-card-heading">
<div class="specialcondolence-card-title">{{row.helpUserName || '--'}}</div>
<div class="specialcondolence-card-code">工号:{{row.helpLoginName || '--'}}</div>
</div>
<enum-tag class="specialcondolence-card-status" v-if="row.instanceState" :value="row.instanceState"
name="ProcessInstanceStateEnum" label_key="message" size="small"></enum-tag>
</div>
</template>
<template v-slot="{index,row}">
<table-column label="慰问对象">{{row.helpUserName}}</table-column>
<table-column label="慰问对象工号">{{row.helpLoginName}}</table-column>
<table-column label="申请人">{{row.applyUserName}}</table-column>
<table-column label="所属工会">{{row.applyUnionName}}</table-column>
<table-column label="所属单位">{{row.applyUnitName}}</table-column>
<table-column label="慰问类型">{{row.typeName}}</table-column>
<table-column label="当前节点">{{row.curTaskName}}</table-column>
</template>
<template #actions="{index,row}">
<div class="action-btn" @click="onView(row)">
<van-icon name="eye-o"></van-icon>
<span>查看</span>
</div>
<div class="action-btn" v-if="row.taskState === 10" @click="onApproval(row)">
<van-icon name="edit"></van-icon>
<span>审核</span>
</div>
<div class="action-btn delete" v-if="row.canRevoke" @click="onRevoke(row)">
<van-icon name="revoke"></van-icon>
<span>撤回</span>
</div>
</template>
<template #empty>
<div class="specialcondolence-list-empty">
<img src="/assets/mobile/img/condolence/condolence-empty.png" alt="暂无慰问审核记录插画">
<div class="specialcondolence-list-empty__title">暂无慰问审核记录</div>
<div class="specialcondolence-list-empty__hint">当前筛选条件下暂时没有慰问申请</div>
</div>
</template>
</table-list>
</main>
<info ref="infoRef" page-key="specialcondolence-school-principal-detail">
<div v-if="showApprovalForm">
<section class="specialcondolence-info-card">
<h2 class="specialcondolence-info-card__title">
审批意见<span class="specialcondolence-audit-card__required">*</span>
</h2>
<van-form ref="formRef">
<van-field
v-model="formData.tf_opinion"
class="specialcondolence-audit-field"
name="tf_opinion"
type="textarea"
rows="6"
placeholder="请输入审批意见"
:rules="[{ required: true, message: '请填写审批意见' }]"
maxlength="100"
show-word-limit
></van-field>
<h2 class="specialcondolence-info-card__title">
审批签字<span class="specialcondolence-audit-card__required">*</span>
</h2>
<van-field name="tf_sign" :value="formData.tf_sign" :rules="[{required:true,message:'请完成审批签字'}]">
<template #input><h5-signature v-model="formData.tf_sign" :key="formData.processTaskId"></h5-signature></template>
</van-field>
</van-form>
</section>
<div class="specialcondolence-popup-actions">
<van-button class="specialcondolence-action-danger" type="danger" block :loading="formLoading" @click="handleTaskAction(6)">退回</van-button>
<van-button class="specialcondolence-action-danger" type="danger" block :loading="formLoading" @click="handleTaskAction(2)">不同意</van-button>
<van-button class="specialcondolence-action-primary" type="primary" block :loading="formLoading" @click="handleTaskAction(1)">同意</van-button>
</div>
</div>
</info>
<van-dialog
:value="confirmVisible"
title="提示"
:message="confirmActionType === 'revoke' ? '您确定要撤回吗?' : '您确定要提交吗?'"
show-cancel-button
confirm-button-color="#1989fa"
@confirm="confirmPendingAction"
@cancel="closeActionConfirm"
></van-dialog>
</div>
</div>
<script nonce="${cspNonce!}">
<!--#include('../common/info.js'){}#-->
new Vue({
el: "#app",
store,
components: {
info: specialcondolenceInfo
},
data() {
return {
pageForm: {
pageNumber: 1,
pageSize: 10,
totalCount: 0,
searchKeyword: '',
approvalText: "0",
approval: false,
year: new Date().getFullYear(),
type: null,
},
typeOptions: [],
approvalOptions: [
{text: "未审核", value: "0"},
{text: "已审核", value: "1"}
],
skeletonLoading: true,
formData: {},
showApprovalForm: false,
formLoading: false,
confirmVisible: false,
confirmActionType: "task",
pendingSubmitType: null,
pendingRevokeRow: null,
historyLayerUnsubscribe: null
}
},
methods: {
// state.initial 表示当前查询前列表为空,仅此阶段使用整组卡片骨架屏。
handleListLoadingChange(state) {
if (state.initial) this.$set(this, "skeletonLoading", state.loading)
},
// 审核状态下拉值为字符串,接口参数 approval 继续使用布尔值。
onApprovalFilterChange(val) {
this.$set(this.pageForm, "approval", val === "1")
this.doSearch()
},
onReady() {
this.querySpecialCondolenceType()
},
onView(row) {
this.$set(this, "showApprovalForm", false)
this.$refs.infoRef.onOpen(row, "慰问详情")
},
onApproval(row) {
this.$set(this, "showApprovalForm", true)
this.$set(this, "formData", {
processTaskId: row.taskId,
taskName: row.curTaskName,
tf_sign: "",
tf_opinion: ""
})
this.$refs.infoRef.onOpen(row, row.curTaskName || "慰问审核")
},
// val 为流程提交类型:1 同意、20 不同意、6 退回;校验通过后打开历史栈确认框。
handleTaskAction(val) {
this.$refs.formRef.validate()
.then(() => {
this.$set(this, "confirmActionType", "task")
this.$set(this, "pendingSubmitType", val)
if (window.h5HistoryLayerManager && window.h5HistoryLayerManager.open("specialcondolence-action-confirm")) return
this.$set(this, "confirmVisible", true)
})
.catch(() => {})
},
// 确认操作时先回退确认框历史,再根据操作类型调用办理或撤回接口。
confirmPendingAction() {
this.closeActionConfirm(() => {
if (this.confirmActionType === "revoke") {
this.revokeTask()
return
}
this.submitTaskAction()
})
},
// 提交 processTaskId、taskName、tf_opinion、tf_sign 和 submitType,接口返回统一响应对象。
submitTaskAction() {
this.$set(this, "formLoading", true)
this.$axios.post("/platform/special_condolence/approval/executeTask", {
data: JSON.stringify({
...this.formData,
submitType: this.pendingSubmitType
})
})
.then((res) => {
if (res && res.code === 0) {
const afterClose = () => {
this.$toast.success(res.msg)
this.doSearch()
}
if (window.h5HistoryLayerManager && window.h5HistoryLayerManager.closeAll(afterClose)) return
this.$refs.infoRef.onClose(afterClose)
}
})
.finally(() => {
this.$set(this, "formLoading", false)
})
},
closeActionConfirm(afterClose = null) {
if (window.h5HistoryLayerManager && window.h5HistoryLayerManager.close("specialcondolence-action-confirm", afterClose)) return
this.$set(this, "confirmVisible", false)
if (typeof afterClose === "function") afterClose()
},
// row 提供待撤回任务 taskId,确认后保存当前行并由统一确认框执行撤回。
onRevoke(row){
this.$set(this, "confirmActionType", "revoke")
this.$set(this, "pendingRevokeRow", row)
if (window.h5HistoryLayerManager && window.h5HistoryLayerManager.open("specialcondolence-action-confirm")) return
this.$set(this, "confirmVisible", true)
},
// 使用 pendingRevokeRow.taskId 撤回任务,接口返回统一响应对象。
revokeTask() {
if (!this.pendingRevokeRow) return
this.$set(this, "formLoading", true)
this.$axios.post("/flow/common/revokeTask", {taskId: this.pendingRevokeRow.taskId})
.then((res) => {
if (res.code === 0) {
this.$toast.success(res.msg)
this.doSearch()
}
})
.finally(() => {
this.$set(this, "formLoading", false)
})
},
querySpecialCondolenceType() {
this.$axios.post('/platform/special_condolence/type/querySpecialCondolenceType', {}, {h5SkeletonLoading: true})
.then((res) => {
const typeList = res.data || []
this.$set(this, "typeOptions", [
{
text: "全部慰问类型",
value: null
}
].concat(typeList.map((v) => ({ text: v.name, value: v.id }))))
if (this.typeOptions.length > 0) {
this.$set(this.pageForm, "type", this.typeOptions[0].value)
this.doSearch()
}
})
},
doSearch() {
this.$nextTick(() => {
this.pageForm.pageNumber = 1
this.pageForm.totalCount = 0
this.$refs.tableListRef.doSearch()
})
}
},
created() {
if (!window.h5HistoryLayerManager) return
this.$set(this, "historyLayerUnsubscribe", window.h5HistoryLayerManager.subscribe((stack) => {
this.$set(this, "confirmVisible", stack.includes("specialcondolence-action-confirm"))
}))
},
beforeDestroy() {
if (typeof this.historyLayerUnsubscribe === "function") this.historyLayerUnsubscribe()
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,350 @@
<!--#
layout("/layouts/platform_h5.html"){
#-->
<style>
#app { --color-primary: #1989fa; }
.specialcondolence-list-page { min-height: 100vh; padding-bottom: calc(24px + env(safe-area-inset-bottom)); box-sizing: border-box; background: #f3f7fd; }
.specialcondolence-list-page .van-nav-bar, .specialcondolence-list-page .van-sticky--fixed { background: #fff; }
.specialcondolence-list-page .van-nav-bar__title { color: #172033; font-size: 16px; font-weight: 600; }
.specialcondolence-list-page .van-nav-bar .van-icon, .specialcondolence-list-page .van-nav-bar__text { color: #1989fa; }
.specialcondolence-list-sticky { padding: 22px 12px 1px; box-sizing: border-box; background: #f3f7fd; }
.specialcondolence-list-search { margin: 0 0 8px; padding: 0; overflow: hidden; border-radius: 12px; background: #fff; box-shadow: 0 5px 16px rgba(43, 73, 112, .1); }
.specialcondolence-list-search .van-search__content { height: 42px; padding-left: 12px; align-items: center; border-radius: 12px; background: #fff; }
.specialcondolence-list-search .van-field__control { color: #263548; font-size: 14px; }
.specialcondolence-list-search .van-field__control::placeholder { color: #a7b0bf; }
.specialcondolence-list-filter { margin-bottom: 8px; overflow: hidden; border-radius: 10px; box-shadow: 0 5px 16px rgba(43, 73, 112, .08); }
.specialcondolence-list-filter .van-dropdown-menu__bar { height: 44px; box-shadow: none; }
.specialcondolence-list-filter .van-dropdown-menu__item, .specialcondolence-list-filter .van-dropdown-menu__title { height: 44px; }
.specialcondolence-list-filter .van-dropdown-menu__item { align-items: center; justify-content: center; }
.specialcondolence-list-filter .van-dropdown-menu__title { display: inline-flex; align-items: center; line-height: 20px; color: #66758a; font-size: 14px; }
.specialcondolence-list-filter .van-dropdown-menu__title--active, .specialcondolence-list-filter .van-dropdown-item__option--active, .specialcondolence-list-filter .van-dropdown-item__option--active .van-dropdown-item__icon { color: #1989fa; }
.specialcondolence-list-filter .van-dropdown-item__content { width: calc(100% - 24px); margin: 0 12px; overflow: hidden; box-sizing: border-box; border-radius: 0 0 12px 12px; box-shadow: 0 10px 24px rgba(31, 65, 108, .14); }
.specialcondolence-list-filter .van-dropdown-item__option { display: flex; min-height: 48px; padding: 0 16px; align-items: center; color: #46566c; font-size: 14px; }
.specialcondolence-list-filter .van-dropdown-item__option .van-cell__title { display: flex; min-height: 20px; align-items: center; line-height: 20px; }
.specialcondolence-list-content { padding: 0 12px; }
.specialcondolence-list-skeleton { padding-top: 2px; }
.specialcondolence-list-skeleton__card { margin-bottom: 12px; padding: 18px 14px 14px; border: 1px solid #e9eff7; border-radius: 12px; background: #fff; box-shadow: 0 5px 16px rgba(43, 73, 112, .08); }
.specialcondolence-list-skeleton__card .van-skeleton { padding: 0; }
.specialcondolence-list-skeleton__card .van-skeleton__title { width: 42%; height: 18px; margin-bottom: 18px; }
.specialcondolence-list-skeleton__card .van-skeleton__row { height: 14px; margin-top: 12px; }
.specialcondolence-list-skeleton__card .van-skeleton__row:nth-child(2n) { width: 72% !important; }
.specialcondolence-list-skeleton__card .van-skeleton__row:last-child { width: 34% !important; margin-top: 18px; margin-left: auto; }
.specialcondolence-list-page .table-list-container { margin-top: 0; padding-bottom: 2px; }
.specialcondolence-list-page .table-list-container .table-list-item { margin-bottom: 12px; padding: 14px; border: 1px solid #e9eff7; border-radius: 12px; box-shadow: 0 5px 16px rgba(43, 73, 112, .08); }
.specialcondolence-card-header { display: flex; margin-bottom: 8px; align-items: flex-start; justify-content: space-between; }
.specialcondolence-card-heading { min-width: 0; padding-right: 10px; flex: 1; }
.specialcondolence-card-title { overflow: hidden; color: #253247; font-size: 15px; font-weight: 600; line-height: 22px; text-overflow: ellipsis; white-space: nowrap; }
.specialcondolence-card-code { margin-top: 1px; color: #9ba6b6; font-size: 12px; line-height: 18px; }
.specialcondolence-card-status { display: inline-flex; flex: none; }
.specialcondolence-card-status .van-tag { min-height: 24px; padding: 3px 9px; border: 0; border-radius: 12px; box-sizing: border-box; font-size: 12px; line-height: 18px; }
.specialcondolence-list-page .table-list-container .table-list-item .item-actions { margin-top: 10px; padding-top: 10px; border-top-color: #edf1f6; }
.specialcondolence-list-page .table-list-container .table-list-item .action-btn { min-height: 28px; padding: 4px 11px; border-radius: 14px; color: #1989fa; background: #eaf4ff; font-size: 12px; line-height: 18px; }
.specialcondolence-list-page .table-list-container .table-list-item .action-btn.delete { color: #f04444; background: #fff0f0; }
.specialcondolence-list-page .table-list-container .table-list-item .action-btn i { margin-right: 5px; font-size: 13px; }
.specialcondolence-list-page .empty-state { padding: 0; }
.specialcondolence-list-empty { display: flex; min-height: 280px; padding: 46px 18px 36px; align-items: center; justify-content: center; box-sizing: border-box; flex-direction: column; text-align: center; }
.specialcondolence-list-empty img { display: block; width: 86%; max-width: 290px; height: auto; object-fit: contain; }
.specialcondolence-list-empty__title { margin-top: 14px; color: #50627a; font-size: 15px; font-weight: 600; line-height: 22px; }
.specialcondolence-list-empty__hint { margin-top: 5px; color: #9aa7b8; font-size: 12px; line-height: 18px; }
</style>
<style id="style-specialcondolence-info-h5">
<!--#include("../common/info.css"){}#-->
</style>
<div id="app">
<div class="specialcondolence-list-page">
<van-nav-bar title="校工会主席审批" left-text="返回" left-arrow @click-left="historyBack" fixed placeholder></van-nav-bar>
<van-sticky offset-top="46px">
<div class="specialcondolence-list-sticky">
<van-search
v-model="pageForm.searchKeyword"
class="specialcondolence-list-search"
placeholder="请输入姓名或者工号搜索"
shape="round"
clearable
@search="doSearch"
@clear="doSearch"
></van-search>
<van-dropdown-menu class="specialcondolence-list-filter" :close-on-click-outside="false" :close-on-click-overlay="false">
<year-van-dropdown-item :num="5" @change="doSearch" v-model="pageForm.year"></year-van-dropdown-item>
<van-dropdown-item v-model="pageForm.type" :options="typeOptions" :multiple="false"
@change="doSearch"></van-dropdown-item>
<van-dropdown-item v-model="pageForm.approvalText" :options="approvalOptions" :multiple="false"
@change="onApprovalFilterChange"></van-dropdown-item>
</van-dropdown-menu>
</div>
</van-sticky>
<main class="specialcondolence-list-content">
<div class="specialcondolence-list-skeleton" v-if="skeletonLoading">
<div class="specialcondolence-list-skeleton__card" v-for="index in 3" :key="'school-union-approval-skeleton-' + index">
<van-skeleton title :row="5" animated></van-skeleton>
</div>
</div>
<table-list api="/platform/special_condolence/schoolUnionApproval/pageData" :page_form.sync="pageForm" ref="tableListRef"
v-show="!skeletonLoading" :show_loading_overlay="false" @ready="onReady"
@loading-change="handleListLoadingChange">
<template #header="{index,row}">
<div class="specialcondolence-card-header">
<div class="specialcondolence-card-heading">
<div class="specialcondolence-card-title">{{row.helpUserName || '--'}}</div>
<div class="specialcondolence-card-code">工号:{{row.helpLoginName || '--'}}</div>
</div>
<enum-tag class="specialcondolence-card-status" v-if="row.instanceState" :value="row.instanceState"
name="ProcessInstanceStateEnum" label_key="message" size="small"></enum-tag>
</div>
</template>
<template v-slot="{index,row}">
<table-column label="慰问对象">{{row.helpUserName}}</table-column>
<table-column label="慰问对象工号">{{row.helpLoginName}}</table-column>
<table-column label="申请人">{{row.applyUserName}}</table-column>
<table-column label="所属工会">{{row.applyUnionName}}</table-column>
<table-column label="所属单位">{{row.applyUnitName}}</table-column>
<table-column label="慰问类型">{{row.typeName}}</table-column>
<table-column label="当前节点">{{row.curTaskName}}</table-column>
</template>
<template #actions="{index,row}">
<div class="action-btn" @click="onView(row)">
<van-icon name="eye-o"></van-icon>
<span>查看</span>
</div>
<div class="action-btn" v-if="row.taskState === 10" @click="onApproval(row)">
<van-icon name="edit"></van-icon>
<span>审核</span>
</div>
<div class="action-btn delete" v-if="row.canRevoke" @click="onRevoke(row)">
<van-icon name="revoke"></van-icon>
<span>撤回</span>
</div>
</template>
<template #empty>
<div class="specialcondolence-list-empty">
<img src="/assets/mobile/img/condolence/condolence-empty.png" alt="暂无慰问审核记录插画">
<div class="specialcondolence-list-empty__title">暂无慰问审核记录</div>
<div class="specialcondolence-list-empty__hint">当前筛选条件下暂时没有慰问申请</div>
</div>
</template>
</table-list>
</main>
<info ref="infoRef" page-key="specialcondolence-school-union-detail">
<div v-if="showApprovalForm">
<section class="specialcondolence-info-card">
<h2 class="specialcondolence-info-card__title">
审批意见<span class="specialcondolence-audit-card__required">*</span>
</h2>
<van-form ref="formRef">
<van-field
v-model="formData.tf_opinion"
class="specialcondolence-audit-field"
name="tf_opinion"
type="textarea"
rows="6"
placeholder="请输入审批意见"
:rules="[{ required: true, message: '请填写审批意见' }]"
maxlength="100"
show-word-limit
></van-field>
<h2 class="specialcondolence-info-card__title">
审批签字<span class="specialcondolence-audit-card__required">*</span>
</h2>
<van-field name="tf_sign" :value="formData.tf_sign" :rules="[{required:true,message:'请完成审批签字'}]">
<template #input><h5-signature v-model="formData.tf_sign" :key="formData.processTaskId"></h5-signature></template>
</van-field>
</van-form>
</section>
<div class="specialcondolence-popup-actions">
<van-button class="specialcondolence-action-danger" type="danger" block :loading="formLoading" @click="handleTaskAction(6)">退回</van-button>
<van-button class="specialcondolence-action-danger" type="danger" block :loading="formLoading" @click="handleTaskAction(2)">不同意</van-button>
<van-button class="specialcondolence-action-primary" type="primary" block :loading="formLoading" @click="handleTaskAction(1)">同意</van-button>
</div>
</div>
</info>
<van-dialog
:value="confirmVisible"
title="提示"
:message="confirmActionType === 'revoke' ? '您确定要撤回吗?' : '您确定要提交吗?'"
show-cancel-button
confirm-button-color="#1989fa"
@confirm="confirmPendingAction"
@cancel="closeActionConfirm"
></van-dialog>
</div>
</div>
<script nonce="${cspNonce!}">
<!--#include('../common/info.js'){}#-->
new Vue({
el: "#app",
store,
components: {
info: specialcondolenceInfo
},
data() {
return {
pageForm: {
pageNumber: 1,
pageSize: 10,
totalCount: 0,
searchKeyword: '',
approvalText: "0",
approval: false,
year: new Date().getFullYear(),
type: null,
},
typeOptions: [],
approvalOptions: [
{text: "未审核", value: "0"},
{text: "已审核", value: "1"}
],
skeletonLoading: true,
formData: {},
showApprovalForm: false,
formLoading: false,
confirmVisible: false,
confirmActionType: "task",
pendingSubmitType: null,
pendingRevokeRow: null,
historyLayerUnsubscribe: null
}
},
methods: {
// state.initial 表示当前查询前列表为空,仅此阶段使用整组卡片骨架屏。
handleListLoadingChange(state) {
if (state.initial) this.$set(this, "skeletonLoading", state.loading)
},
// 审核状态下拉值为字符串,接口参数 approval 继续使用布尔值。
onApprovalFilterChange(val) {
this.$set(this.pageForm, "approval", val === "1")
this.doSearch()
},
onReady() {
this.querySpecialCondolenceType()
},
onView(row) {
this.$set(this, "showApprovalForm", false)
this.$refs.infoRef.onOpen(row, "慰问详情")
},
onApproval(row) {
this.$set(this, "showApprovalForm", true)
this.$set(this, "formData", {
processTaskId: row.taskId,
taskName: row.curTaskName,
tf_sign: "",
tf_opinion: ""
})
this.$refs.infoRef.onOpen(row, row.curTaskName || "慰问审核")
},
// val 为流程提交类型:1 同意、20 不同意、6 退回;校验通过后打开历史栈确认框。
handleTaskAction(val) {
this.$refs.formRef.validate()
.then(() => {
this.$set(this, "confirmActionType", "task")
this.$set(this, "pendingSubmitType", val)
if (window.h5HistoryLayerManager && window.h5HistoryLayerManager.open("specialcondolence-action-confirm")) return
this.$set(this, "confirmVisible", true)
})
.catch(() => {})
},
// 确认操作时先回退确认框历史,再根据操作类型调用办理或撤回接口。
confirmPendingAction() {
this.closeActionConfirm(() => {
if (this.confirmActionType === "revoke") {
this.revokeTask()
return
}
this.submitTaskAction()
})
},
// 提交 processTaskId、taskName、tf_opinion、tf_sign 和 submitType,接口返回统一响应对象。
submitTaskAction() {
this.$set(this, "formLoading", true)
this.$axios.post("/platform/special_condolence/approval/executeTask", {
data: JSON.stringify({
...this.formData,
submitType: this.pendingSubmitType
})
})
.then((res) => {
if (res && res.code === 0) {
const afterClose = () => {
this.$toast.success(res.msg)
this.doSearch()
}
if (window.h5HistoryLayerManager && window.h5HistoryLayerManager.closeAll(afterClose)) return
this.$refs.infoRef.onClose(afterClose)
}
})
.finally(() => {
this.$set(this, "formLoading", false)
})
},
closeActionConfirm(afterClose = null) {
if (window.h5HistoryLayerManager && window.h5HistoryLayerManager.close("specialcondolence-action-confirm", afterClose)) return
this.$set(this, "confirmVisible", false)
if (typeof afterClose === "function") afterClose()
},
// row 提供待撤回任务 taskId,确认后保存当前行并由统一确认框执行撤回。
onRevoke(row){
this.$set(this, "confirmActionType", "revoke")
this.$set(this, "pendingRevokeRow", row)
if (window.h5HistoryLayerManager && window.h5HistoryLayerManager.open("specialcondolence-action-confirm")) return
this.$set(this, "confirmVisible", true)
},
// 使用 pendingRevokeRow.taskId 撤回任务,接口返回统一响应对象。
revokeTask() {
if (!this.pendingRevokeRow) return
this.$set(this, "formLoading", true)
this.$axios.post("/flow/common/revokeTask", {taskId: this.pendingRevokeRow.taskId})
.then((res) => {
if (res.code === 0) {
this.$toast.success(res.msg)
this.doSearch()
}
})
.finally(() => {
this.$set(this, "formLoading", false)
})
},
querySpecialCondolenceType() {
this.$axios.post('/platform/special_condolence/type/querySpecialCondolenceType', {}, {h5SkeletonLoading: true})
.then((res) => {
const typeList = res.data || []
this.$set(this, "typeOptions", [
{
text: "全部慰问类型",
value: null
}
].concat(typeList.map((v) => ({ text: v.name, value: v.id }))))
if (this.typeOptions.length > 0) {
this.$set(this.pageForm, "type", this.typeOptions[0].value)
this.doSearch()
}
})
},
doSearch() {
this.$nextTick(() => {
this.pageForm.pageNumber = 1
this.pageForm.totalCount = 0
this.$refs.tableListRef.doSearch()
})
}
},
created() {
if (!window.h5HistoryLayerManager) return
this.$set(this, "historyLayerUnsubscribe", window.h5HistoryLayerManager.subscribe((stack) => {
this.$set(this, "confirmVisible", stack.includes("specialcondolence-action-confirm"))
}))
},
beforeDestroy() {
if (typeof this.historyLayerUnsubscribe === "function") this.historyLayerUnsubscribe()
}
})
</script>
<!--#
}
#-->