医疗互助补助模块

This commit is contained in:
2026-07-23 11:57:08 +08:00
parent 67e40e36ea
commit a17140fbd2
35 changed files with 5607 additions and 7 deletions
@@ -27,14 +27,13 @@ import java.util.Map;
@IocBean
public class SmsJshvcServiceImpl implements SmsService {
private static final String APPID = "1430576717768122368";
private static final String APP_SECRET = "19A0ACB03FBQHL4R4XQD";
private static final String TOKEN_URL = "https://gateway.jshvc.edu.cn/token/gateway/accessToken";
private static final String MSG_URL = "https://gateway.jshvc.edu.cn/mp/restful/v2/message/send";
private static final String REDIS_KEY_MSG_ACCESS_TOKEN = "msg:token:";
private static final String APPID = "";
private static final String APP_SECRET = "";
private static final String TOKEN_URL = "";
private static final String MSG_URL = "";
private static final String REDIS_KEY_MSG_ACCESS_TOKEN = "";
@Inject
@Inject
private RedisService redisService;
@Override
@@ -0,0 +1,86 @@
package com.budwk.app.zhgh.staffbenefit.medicalMutualAid.apply.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import com.budwk.app.base.result.Result;
import com.budwk.app.zhgh.staffbenefit.medicalMutualAid.apply.model.MutualApply;
import com.budwk.app.zhgh.staffbenefit.medicalMutualAid.apply.service.MedicalMutualAidApplyService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
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;
/**
* 互助金申请页面控制器,负责申请表单页面入口和表单保存、提交接口。
*/
@IocBean
@At("/platform/medicalMutualAid/apply")
@Api("互助金申请")
@Ok("json:full")
public class MedicalMutualAidApplyFormController {
@Inject
private MedicalMutualAidApplyService medicalMutualAidApplyService;
/**
* 打开互助金申请页面。
*/
@At("/form")
@Ok("beetl:/platform/zhgh/staffbenefit/medicalMutualAid/apply/form/index.html")
@SaCheckPermission("medicalMutualAid.apply.form")
public void index() {
}
/**
* 查询申请页初始化数据。
*
* @return 当前用户占位信息和疾病下拉数据
*/
@At("/form/initApplyData")
@ApiOperation("查询互助金申请初始化数据")
@SaCheckPermission("medicalMutualAid.apply.form")
public Result initApplyData() {
return Result.success(medicalMutualAidApplyService.initApplyData());
}
/**
* 查询申请详情,用于我的申请查看和编辑复用申请表单页面。
*
* @param id 申请ID
* @return 申请主表详情
*/
@At("/form/detail")
@ApiOperation("查询互助金申请详情")
@SaCheckPermission("medicalMutualAid.apply.form")
public Result detail(@Param("id") Long id) {
return Result.success(medicalMutualAidApplyService.applyDetail(id));
}
/**
* 保存草稿。
*
* @param apply 申请表单
* @return 保存后的申请数据
*/
@At("/form/saveDraft")
@ApiOperation("保存互助金申请草稿")
@SaCheckPermission("medicalMutualAid.apply.form")
public Result saveDraft(MutualApply apply) {
return Result.success(medicalMutualAidApplyService.saveApply(apply, false));
}
/**
* 提交申请。
*
* @param apply 申请表单
* @return 提交后的申请数据
*/
@At("/form/submitApply")
@ApiOperation("提交互助金申请")
@SaCheckPermission("medicalMutualAid.apply.form")
public Result submitApply(MutualApply apply) {
return Result.success(medicalMutualAidApplyService.saveApply(apply, true));
}
}
@@ -0,0 +1,127 @@
package com.budwk.app.zhgh.staffbenefit.medicalMutualAid.apply.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.result.Result;
import com.budwk.app.zhgh.staffbenefit.medicalMutualAid.apply.service.MedicalMutualAidApplyService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
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;
/**
* 互助会审核控制器,负责审核列表、审核详情页面入口以及审核相关接口。
*/
@IocBean
@At("/platform/medicalMutualAid/apply")
@Api("互助会审核")
@Ok("json:full")
public class MedicalMutualAidAuditController {
@Inject
private MedicalMutualAidApplyService medicalMutualAidApplyService;
/**
* 打开互助会审核列表页面。
*/
@At("/audit")
@Ok("beetl:/platform/zhgh/staffbenefit/medicalMutualAid/apply/audit/index.html")
@SaCheckPermission("medicalMutualAid.apply.audit")
public void index() {
}
/**
* 查询互助会审核列表。
*
* @param pageForm 分页参数
* @param keyword 申请人工号或申请人姓名
* @param diseaseName 大病名称
* @param status 审核状态
* @return 审核分页数据
*/
@At("/audit/page")
@ApiOperation("查询互助会审核列表")
@SaCheckPermission("medicalMutualAid.apply.audit")
public Result page(PageForm pageForm, String keyword, String diseaseName, Integer status) {
return Result.success(medicalMutualAidApplyService.auditPage(pageForm, keyword, diseaseName, status));
}
/**
* 打开互助会审核详情页面。
*/
@At("/audit/review")
@Ok("beetl:/platform/zhgh/staffbenefit/medicalMutualAid/apply/audit/review.html")
@SaCheckPermission("medicalMutualAid.apply.audit")
public void review() {
}
/**
* 查询审核详情。
*
* @param id 申请ID
* @return 审核详情和动态补助明细
*/
@At("/audit/review/detail")
@ApiOperation("查询互助会审核详情")
@SaCheckPermission("medicalMutualAid.apply.audit")
public Result detail(Long id) {
return Result.success(medicalMutualAidApplyService.auditDetail(id));
}
/**
* 审核通过。
*
* @param id 申请ID
* @param bonusRatio 补助系数
* @param actualSubsidyAmount 实际补助金额
* @param remark 审核备注
* @return 审核后的详情数据
*/
@At("/audit/review/approve")
@ApiOperation("互助会审核通过")
@SaCheckPermission("medicalMutualAid.apply.audit")
public Result approve(Long id, String bonusRatio, String actualSubsidyAmount, String remark) {
return Result.success(medicalMutualAidApplyService.approve(id, bonusRatio, actualSubsidyAmount, remark));
}
/**
* 弃审已审核申请,恢复为未审核状态并清理审核补助结果。
*
* @param id 申请ID
* @return 弃审后的申请数据
*/
@At("/audit/cancelAudit")
@ApiOperation("互助会弃审")
@SaCheckPermission("medicalMutualAid.apply.audit")
public Result cancelAudit(Long id) {
return Result.success(medicalMutualAidApplyService.cancelAudit(id));
}
/**
* 作废未审核申请,状态置为作废并记录审核人和审核时间。
*
* @param id 申请ID
* @return 作废后的申请数据
*/
@At("/audit/discard")
@ApiOperation("互助会作废")
@SaCheckPermission("medicalMutualAid.apply.audit")
public Result discard(Long id) {
return Result.success(medicalMutualAidApplyService.discard(id));
}
/**
* 取消作废申请,状态恢复为未审核并清空审核人和审核时间。
*
* @param id 申请ID
* @return 取消作废后的申请数据
*/
@At("/audit/cancelDiscard")
@ApiOperation("互助会取消作废")
@SaCheckPermission("medicalMutualAid.apply.audit")
public Result cancelDiscard(Long id) {
return Result.success(medicalMutualAidApplyService.cancelDiscard(id));
}
}
@@ -0,0 +1,143 @@
package com.budwk.app.zhgh.staffbenefit.medicalMutualAid.apply.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
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.zhgh.staffbenefit.medicalMutualAid.apply.model.MutualApply;
import com.budwk.app.zhgh.staffbenefit.medicalMutualAid.apply.service.MedicalMutualAidApplyService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.nutz.lang.util.NutMap;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
import org.nutz.mvc.annotation.Param;
import javax.servlet.http.HttpServletResponse;
/**
* 我的申请页面控制器,负责个人申请列表页面入口和分页查询接口。
*/
@IocBean
@At("/platform/medicalMutualAid/apply")
@Api("我的互助金申请")
@Ok("json:full")
public class MedicalMutualAidMineApplyController {
@Inject
private MedicalMutualAidApplyService medicalMutualAidApplyService;
/**
* 打开我的申请页面。
*/
@At("/mine")
@Ok("beetl:/platform/zhgh/staffbenefit/medicalMutualAid/apply/mine/index.html")
@SaCheckPermission("medicalMutualAid.apply.mine")
public void index() {
}
/**
* 打开我的申请查看/编辑页面,页面入口独立于新增申请页,避免新增逻辑与个人记录维护逻辑互相影响。
*/
@At("/mine/form")
@Ok("beetl:/platform/zhgh/staffbenefit/medicalMutualAid/apply/mine/form.html")
@SaCheckPermission("medicalMutualAid.apply.mine")
public void form() {
}
/**
* 查询我的申请维护页初始化数据,当前主要用于加载疾病类型下拉。
*
* @return 页面初始化数据
*/
@At("/mine/form/initApplyData")
@ApiOperation("查询我的互助金申请维护页初始化数据")
@SaCheckPermission("medicalMutualAid.apply.mine")
public Result initApplyData() {
return Result.success(medicalMutualAidApplyService.initApplyData());
}
/**
* 查询我的申请详情,供个人查看和编辑页面回显使用。
*
* @param id 申请ID
* @return 申请详情
*/
@At("/mine/form/detail")
@ApiOperation("查询我的互助金申请详情")
@SaCheckPermission("medicalMutualAid.apply.mine")
public Result detail(@Param("id") Long id) {
return Result.success(medicalMutualAidApplyService.applyDetail(id));
}
/**
* 保存我的申请草稿,复用申请服务保存逻辑但使用我的申请权限入口。
*
* @param apply 申请表单
* @return 保存后的申请数据
*/
@At("/mine/form/saveDraft")
@ApiOperation("保存我的互助金申请草稿")
@SaCheckPermission("medicalMutualAid.apply.mine")
public Result saveDraft(MutualApply apply) {
return Result.success(medicalMutualAidApplyService.saveApply(apply, false));
}
/**
* 提交我的申请修改,提交后仍进入未审核状态等待审核。
*
* @param apply 申请表单
* @return 提交后的申请数据
*/
@At("/mine/form/submitApply")
@ApiOperation("提交我的互助金申请")
@SaCheckPermission("medicalMutualAid.apply.mine")
public Result submitApply(MutualApply apply) {
return Result.success(medicalMutualAidApplyService.saveApply(apply, true));
}
/**
* 查询我的申请列表。
*
* @param pageForm 分页参数
* @param diseaseName 大病名称
* @return 我的申请分页数据
*/
@At("/mine/page")
@ApiOperation("查询我的互助金申请")
@SaCheckPermission("medicalMutualAid.apply.mine")
public Result page(PageForm pageForm, String diseaseName) {
return Result.success(medicalMutualAidApplyService.minePage(pageForm, diseaseName));
}
/**
* 查询我的申请列表,兼容平台通用 initTableMixins 的默认分页接口命名。
*
* @param pageForm 分页与查询参数
* @param diseaseName 大病名称
* @return 我的申请分页数据
*/
@At("/mine/pageData")
@ApiOperation("查询我的互助金申请")
@SaCheckPermission("medicalMutualAid.apply.mine")
public Result pageData(PageForm pageForm, String diseaseName) {
return Result.success(medicalMutualAidApplyService.minePage(pageForm, diseaseName));
}
/**
* 导出我的申请单,Word 模板来源于系统模板管理,模板编码为 medical_mutual_aid_apply_form。
*
* @param id 申请ID
* @param response HTTP响应
*/
@At("/mine/exportApplyForm")
@Ok("void")
@ApiOperation("导出我的大病医疗互助金申请单")
@SaCheckPermission("medicalMutualAid.apply.mine")
public void exportApplyForm(@Param("id") Long id, HttpServletResponse response) {
NutMap exportData = medicalMutualAidApplyService.exportMineApplyForm(id);
CommonDownloadUtil.download(exportData.getString("fileName"), (byte[]) exportData.get("bytes"), response);
}
}
@@ -0,0 +1,71 @@
package com.budwk.app.zhgh.staffbenefit.medicalMutualAid.apply.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
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.zhgh.staffbenefit.medicalMutualAid.apply.service.MedicalMutualAidApplyService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.apache.poi.ss.usermodel.Workbook;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
import javax.servlet.http.HttpServletResponse;
/**
* 医疗互助汇总控制器,负责汇总页面入口和审核通过数据的汇总查询接口。
*/
@IocBean
@At("/platform/medicalMutualAid/apply")
@Api("医疗互助汇总")
@Ok("json:full")
public class MedicalMutualAidSummaryController {
@Inject
private MedicalMutualAidApplyService medicalMutualAidApplyService;
/**
* 打开医疗互助汇总页面。
*/
@At("/summary")
@Ok("beetl:/platform/zhgh/staffbenefit/medicalMutualAid/apply/summary/index.html")
@SaCheckPermission("medicalMutualAid.apply.summary")
public void index() {
}
/**
* 查询医疗互助汇总列表。
*
* @param pageForm 分页参数
* @param deptName 所在单位
* @param userName 姓名
* @param joinYear 加入年份
* @return 汇总分页数据
*/
@At("/summary/page")
@ApiOperation("查询医疗互助汇总")
@SaCheckPermission("medicalMutualAid.apply.summary")
public Result page(PageForm pageForm, String deptName, String userName, Integer joinYear) {
return Result.success(medicalMutualAidApplyService.summaryPage(pageForm, deptName, userName, joinYear));
}
/**
* 导出医疗互助汇总,导出列与页面当前展示列保持一致。
*
* @param deptName 所在单位
* @param userName 姓名
* @param joinYear 加入年份
* @param response HTTP 响应
*/
@At("/summary/exportExcel")
@Ok("void")
@ApiOperation("导出医疗互助汇总")
@SaCheckPermission("medicalMutualAid.apply.summary")
public void exportExcel(String deptName, String userName, Integer joinYear, HttpServletResponse response) {
Workbook workbook = medicalMutualAidApplyService.exportSummary(deptName, userName, joinYear);
CommonDownloadUtil.download("医疗互助汇总.xlsx", workbook, response);
}
}
@@ -0,0 +1,269 @@
package com.budwk.app.zhgh.staffbenefit.medicalMutualAid.apply.model;
import lombok.Data;
import org.nutz.dao.entity.annotation.ColDefine;
import org.nutz.dao.entity.annotation.ColType;
import org.nutz.dao.entity.annotation.Column;
import org.nutz.dao.entity.annotation.Comment;
import org.nutz.dao.entity.annotation.Default;
import org.nutz.dao.entity.annotation.Id;
import org.nutz.dao.entity.annotation.Table;
import org.nutz.dao.entity.annotation.TableMeta;
import org.nutz.dao.interceptor.annotation.PrevInsert;
import org.nutz.dao.interceptor.annotation.PrevUpdate;
import java.util.Date;
/**
* 互助金申请主表实体,承载申请人信息、疾病费用、审核状态和审核后的补助汇总结果。
*/
@Data
@Table("tb_mutual_apply")
@TableMeta("{'mysql-charset':'utf8mb4'}")
@Comment("互助金申请表")
public class MutualApply {
/** 申请主键,数据库自增生成。 */
@Id
@Comment("主键")
private Long id;
/** 申请编号,用于后续查询、打印和业务留痕。 */
@Column("apply_no")
@ColDefine(type = ColType.VARCHAR, width = 50)
@Comment("申请编号")
private String applyNo;
/** 申请人用户 ID。 */
@Column("user_id")
@ColDefine(type = ColType.VARCHAR, width = 50)
@Comment("申请人用户ID")
private String userId;
/** 申请人工号或登录名。 */
@Column("login_name")
@ColDefine(type = ColType.VARCHAR, width = 50)
@Comment("申请人工号/登录名")
private String loginName;
/** 申请人姓名。 */
@Column("user_name")
@ColDefine(type = ColType.VARCHAR, width = 50)
@Comment("申请人姓名")
private String userName;
/** 性别。 */
@Column("sex")
@ColDefine(type = ColType.VARCHAR, width = 20)
@Comment("性别")
private String sex;
/** 出生年月。 */
@Column("birthday")
@Comment("出生年月")
private Date birthday;
/** 籍贯。 */
@Column("native_place")
@ColDefine(type = ColType.VARCHAR, width = 100)
@Comment("籍贯")
private String nativePlace;
/** 学历。 */
@Column("education")
@ColDefine(type = ColType.VARCHAR, width = 50)
@Comment("学历")
private String education;
/** 职称。 */
@Column("professional_title")
@ColDefine(type = ColType.VARCHAR, width = 100)
@Comment("职称")
private String professionalTitle;
/** 工作部门 ID。 */
@Column("dept_id")
@ColDefine(type = ColType.VARCHAR, width = 50)
@Comment("工作部门ID")
private String deptId;
/** 工作部门名称。 */
@Column("dept_name")
@ColDefine(type = ColType.VARCHAR, width = 200)
@Comment("工作部门(系部)")
private String deptName;
/** 身份证号码。 */
@Column("id_card")
@ColDefine(type = ColType.VARCHAR, width = 30)
@Comment("身份证号码")
private String idCard;
/** 联系电话。 */
@Column("mobile")
@ColDefine(type = ColType.VARCHAR, width = 30)
@Comment("联系电话")
private String mobile;
/** 工资银行卡号。 */
@Column("bank_card_no")
@ColDefine(type = ColType.VARCHAR, width = 50)
@Comment("工资银行卡号")
private String bankCardNo;
/** 开户行。 */
@Column("bank_name")
@ColDefine(type = ColType.VARCHAR, width = 200)
@Comment("开户行")
private String bankName;
/** 入会时间。 */
@Column("join_date")
@Comment("入会时间")
private Date joinDate;
/** 加入年份。 */
@Column("join_year")
@Comment("加入年份")
private Integer joinYear;
/** 大病类型 ID,来源于疾病类型档案。 */
@Column("disease_type_id")
@Comment("大病类型ID")
private String diseaseTypeId;
/** 大病名称,落表保存历史名称。 */
@Column("disease_name")
@ColDefine(type = ColType.VARCHAR, width = 200)
@Comment("大病名称")
private String diseaseName;
/** 何时何病住何医院。 */
@Column("hospital_desc")
@ColDefine(type = ColType.TEXT)
@Comment("何时何病住何医院")
private String hospitalDesc;
/** 全年医疗费用总金额。 */
@Column("medical_total_amount")
@Default("0.00")
@Comment("全年医疗费用总金额")
private Double medicalTotalAmount;
/** 自付金额。 */
@Column("self_pay_amount")
@Default("0.00")
@Comment("自付金额")
private Double selfPayAmount;
/** 申请人签名文件 ID。 */
@Column("sign_file_id")
@ColDefine(type = ColType.VARCHAR, width = 64)
@Comment("申请人签名文件ID")
private String signFileId;
/** 申请人签名时间。 */
@Column("sign_time")
@Comment("申请人签名时间")
private Date signTime;
/** 申请状态:0 未审核,1 已审核。 */
@Column("status")
@Default("0")
@Comment("状态 0未审核 1已审核")
private Integer status;
/** 提交时间。 */
@Column("submit_time")
@Comment("提交时间")
private Date submitTime;
/** 审核人。 */
@Column("audit_by")
@ColDefine(type = ColType.VARCHAR, width = 50)
@Comment("审核人")
private String auditBy;
/** 审核时间。 */
@Column("audit_time")
@Comment("审核时间")
private Date auditTime;
/** 审核意见。 */
@Column("audit_opinion")
@ColDefine(type = ColType.VARCHAR, width = 500)
@Comment("审核意见")
private String auditOpinion;
/** 审核使用的配置 ID。 */
@Column("config_id")
@Comment("审核使用的互助金配置ID")
private Long configId;
/** 审核使用的配置年度。 */
@Column("config_year")
@Comment("审核使用的配置年度")
private Integer configYear;
/** 区间补助小计。 */
@Column("subsidy_total_amount")
@Comment("补助小计")
private Double subsidyTotalAmount;
/** 奖励或补助系数。 */
@Column("bonus_ratio")
@Comment("奖励/补助系数")
private Double bonusRatio;
/** 实际补助金额。 */
@Column("actual_subsidy_amount")
@Comment("实际补助金额")
private Double actualSubsidyAmount;
/** 连续未申请年数。 */
@Column("continuous_unapply_year")
@Comment("连续未申请年数")
private Integer continuousUnapplyYear;
/** 本次适用最高补助限额。 */
@Column("limit_amount")
@Comment("本次适用最高补助限额")
private Double limitAmount;
/** 当前申报年度同申报人同病种已审核补助金额汇总,仅用于审核页提示展示。 */
private Double yearUsedAmount;
/** 当前申报年度同申报人同病种剩余可申报金额,仅用于审核页提示展示。 */
private Double yearRemainAmount;
/** 备注。 */
@Column("remark")
@ColDefine(type = ColType.VARCHAR, width = 500)
@Comment("备注")
private String remark;
/** 创建人。 */
@Column("create_by")
@ColDefine(type = ColType.VARCHAR, width = 50)
@Comment("创建人")
private String createBy;
/** 创建时间。 */
@Column("create_time")
@PrevInsert(now = true)
@Comment("创建时间")
private Date createTime;
/** 更新人。 */
@Column("update_by")
@ColDefine(type = ColType.VARCHAR, width = 50)
@Comment("更新人")
private String updateBy;
/** 更新时间。 */
@Column("update_time")
@PrevInsert(now = true)
@PrevUpdate(now = true)
@Comment("更新时间")
private Date updateTime;
}
@@ -0,0 +1,86 @@
package com.budwk.app.zhgh.staffbenefit.medicalMutualAid.apply.model;
import lombok.Data;
import org.nutz.dao.entity.annotation.ColDefine;
import org.nutz.dao.entity.annotation.ColType;
import org.nutz.dao.entity.annotation.Column;
import org.nutz.dao.entity.annotation.Comment;
import org.nutz.dao.entity.annotation.Default;
import org.nutz.dao.entity.annotation.Id;
import org.nutz.dao.entity.annotation.Table;
import org.nutz.dao.entity.annotation.TableMeta;
import org.nutz.dao.interceptor.annotation.PrevInsert;
import java.util.Date;
/**
* 互助金动态补贴金额明细实体,一条申请按配置区间拆分为多条补助计算记录。
*/
@Data
@Table("tb_mutual_apply_subsidy")
@TableMeta("{'mysql-charset':'utf8mb4'}")
@Comment("互助金补贴金额明细表")
public class MutualApplySubsidy {
/** 补助明细主键。 */
@Id
@Comment("主键")
private Long id;
/** 所属申请 ID。 */
@Column("apply_id")
@Comment("申请ID")
private Long applyId;
/** 使用的补助比例配置 ID。 */
@Column("ratio_id")
@Comment("使用的补助比例配置ID")
private Long ratioId;
/** 页面展示用的区间名称。 */
@Column("range_name")
@ColDefine(type = ColType.VARCHAR, width = 100)
@Comment("区间名称")
private String rangeName;
/** 区间开始金额。 */
@Column("start_amount")
@Default("0.00")
@Comment("区间开始金额")
private Double startAmount;
/** 区间结束金额,空表示不设上限。 */
@Column("end_amount")
@Comment("区间结束金额(NULL表示无限)")
private Double endAmount;
/** 该区间的补助比例。 */
@Column("ratio")
@Default("0.00")
@Comment("补助比例(%)")
private Double ratio;
/** 本区间参与补助计算的金额。 */
@Column("base_amount")
@Default("0.00")
@Comment("本区间参与计算金额")
private Double baseAmount;
/** 本区间计算出的补助金额。 */
@Column("subsidy_amount")
@Default("0.00")
@Comment("本区间补助金额")
private Double subsidyAmount;
/** 区间排序。 */
@Column("sort")
@Default("1")
@Comment("排序")
private Integer sort;
/** 创建时间。 */
@Column("create_time")
@PrevInsert(now = true)
@Comment("创建时间")
private Date createTime;
}
@@ -0,0 +1,130 @@
package com.budwk.app.zhgh.staffbenefit.medicalMutualAid.apply.service;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.service.BaseService;
import com.budwk.app.zhgh.staffbenefit.medicalMutualAid.apply.model.MutualApply;
import org.apache.poi.ss.usermodel.Workbook;
import org.nutz.lang.util.NutMap;
/**
* 医疗互助申请服务,统一承载申请、我的申请、审核和汇总页面所需的公共业务方法。
*/
public interface MedicalMutualAidApplyService extends BaseService<MutualApply> {
/**
* 构建申请页面初始化数据,包含当前用户占位信息和疾病类型下拉数据。
*
* @return 页面初始化数据
*/
NutMap initApplyData();
/**
* 保存互助金申请,草稿和提交都复用该方法并由 submit 参数控制状态。
*
* @param apply 互助金申请表单
* @param submit 是否提交审核
* @return 保存后的申请数据
*/
MutualApply saveApply(MutualApply apply, boolean submit);
/**
* 查询申请表单详情,用于我的申请查看和编辑时回显原始申请数据。
*
* @param applyId 申请ID
* @return 申请主表数据
*/
MutualApply applyDetail(Long applyId);
/**
* 查询我的申请列表。
*
* @param pageForm 分页参数
* @param diseaseName 大病名称
* @return 我的申请分页列表
*/
Pagination<MutualApply> minePage(PageForm pageForm, String diseaseName);
/**
* 导出我的申请单 Word 文件,模板来源于系统模板管理,参数在服务层统一组装。
*
* @param applyId 申请ID
* @return 包含 fileName 和 bytes 的导出结果
*/
NutMap exportMineApplyForm(Long applyId);
/**
* 查询互助会审核列表。
*
* @param pageForm 分页参数
* @param keyword 申请人工号或申请人姓名
* @param diseaseName 大病名称
* @param status 审核状态
* @return 审核分页列表
*/
Pagination<MutualApply> auditPage(PageForm pageForm, String keyword, String diseaseName, Integer status);
/**
* 查询审核详情并携带动态补助明细。
*
* @param applyId 申请ID
* @return 审核详情数据
*/
NutMap auditDetail(Long applyId);
/**
* 审核通过并保存补助计算结果。
*
* @param applyId 申请ID
* @param bonusRatio 审核页面录入的补助系数
* @param actualSubsidyAmount 实际补助金额
* @param remark 审核备注
* @return 审核后的详情数据
*/
NutMap approve(Long applyId, String bonusRatio, String actualSubsidyAmount, String remark);
/**
* 弃审已审核的互助金申请,将状态恢复为未审核并清理审核结果和补助明细。
*
* @param applyId 申请ID
* @return 弃审后的申请数据
*/
MutualApply cancelAudit(Long applyId);
/**
* 作废未审核的互助金申请,记录作废操作人和操作时间。
*
* @param applyId 申请ID
* @return 作废后的申请数据
*/
MutualApply discard(Long applyId);
/**
* 取消作废互助金申请,将状态恢复为未审核并清空审核人和审核时间。
*
* @param applyId 申请ID
* @return 取消作废后的申请数据
*/
MutualApply cancelDiscard(Long applyId);
/**
* 查询医疗互助汇总列表。
*
* @param pageForm 分页参数
* @param deptName 所在单位
* @param userName 姓名
* @param joinYear 加入年份
* @return 汇总分页列表
*/
Pagination<NutMap> summaryPage(PageForm pageForm, String deptName, String userName, Integer joinYear);
/**
* 导出医疗互助汇总,导出字段与汇总页面展示列保持一致,动态补助区间按当前查询结果生成。
*
* @param deptName 所在单位
* @param userName 姓名
* @param joinYear 加入年份
* @return Excel 工作簿
*/
Workbook exportSummary(String deptName, String userName, Integer joinYear);
}
@@ -0,0 +1,999 @@
package com.budwk.app.zhgh.staffbenefit.medicalMutualAid.apply.service.impl;
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.stp.StpUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
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.service.impl.BaseServiceImpl;
import com.budwk.app.base.utils.SysOfficeTemplateUtil;
import com.budwk.app.zhgh.dayofficework.fund.member.models.FundMemberChangeRecord;
import com.budwk.app.zhgh.staffbenefit.medicalMutualAid.apply.model.MutualApply;
import com.budwk.app.zhgh.staffbenefit.medicalMutualAid.apply.model.MutualApplySubsidy;
import com.budwk.app.zhgh.staffbenefit.medicalMutualAid.apply.service.MedicalMutualAidApplyService;
import com.budwk.app.zhgh.staffbenefit.medicalMutualAid.config.model.MutualBonus;
import com.budwk.app.zhgh.staffbenefit.medicalMutualAid.config.model.MutualConfig;
import com.budwk.app.zhgh.staffbenefit.medicalMutualAid.config.model.MutualRatio;
import com.budwk.app.zhgh.staffbenefit.medicalMutualAid.medical.model.MedicalDiseaseType;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.dao.Chain;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.util.NutMap;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.Font;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.VerticalAlignment;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.util.CellRangeAddress;
import com.deepoove.poi.XWPFTemplate;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* 医疗互助申请服务实现,先搭建申请、审核、计算和汇总的架构入口,细化公式后续可在本类集中调整。
*/
@IocBean(args = {"refer:dao"})
public class MedicalMutualAidApplyServiceImpl extends BaseServiceImpl<MutualApply>
implements MedicalMutualAidApplyService {
/** 我的申请单 Word 导出模板编码,对应系统模板管理中的模板代码。 */
private static final String APPLY_FORM_TEMPLATE_CODE = "medical_mutual_aid_apply_form";
@Inject
private SysOfficeTemplateUtil sysOfficeTemplateUtil;
/**
* 初始化申请服务的数据访问对象。
*
* @param dao 数据访问对象
*/
public MedicalMutualAidApplyServiceImpl(Dao dao) {
super(dao);
}
/**
* 构建申请页面初始数据;当前用户信息先用登录 ID 占位,后续可替换为完整会员档案查询。
*/
@Override
public NutMap initApplyData() {
List<MedicalDiseaseType> diseaseTypes = dao().query(MedicalDiseaseType.class, Cnd.NEW().asc("diseaseCode"));
return NutMap.NEW()
.addv("user", buildCurrentUserPlaceholder())
.addv("diseaseTypes", diseaseTypes);
}
/**
* 保存申请主表,提交时设置未审核状态和提交时间,草稿时仅保存表单内容。
*/
@Override
public MutualApply saveApply(MutualApply apply, boolean submit) {
if (apply == null) {
throw new BaseException("申请信息不能为空");
}
// 已有申请只允许未审核状态继续保存,防止已审核或已作废数据被个人端再次修改。
if (apply.getId() != null) {
MutualApply oldApply = fetch(apply.getId());
if (oldApply == null) {
throw new BaseException("申请记录不存在");
}
if (oldApply.getStatus() == null || oldApply.getStatus() != 0) {
throw new BaseException("已审核或已作废的申请不能修改");
}
}
fillApplyDefaults(apply, submit);
fillOperator(apply);
return insertOrUpdate(apply);
}
/**
* 查询申请详情,供申请表单在查看和编辑模式下回显。
*/
@Override
public MutualApply applyDetail(Long applyId) {
if (applyId == null) {
throw new BaseException("申请ID不能为空");
}
MutualApply apply = fetch(applyId);
if (apply == null) {
throw new BaseException("申请记录不存在");
}
return apply;
}
/**
* 查询我的申请;架构阶段先按当前登录 ID 过滤,未登录时返回全部方便本地页面联调。
*/
@Override
public Pagination<MutualApply> minePage(PageForm pageForm, String diseaseName) {
Cnd cnd = Cnd.NEW();
String loginId = getLoginId();
if (StrUtil.isNotBlank(loginId)) {
cnd.and("createBy", "=", loginId);
}
appendDiseaseName(cnd, diseaseName);
return listPage(pageForm.getPageNumber(), pageForm.getPageSize(), cnd.desc("createTime"));
}
/**
* 使用系统模板管理中的 Word 模板导出个人申请单,模板参数由 buildMineApplyFormDocData 统一维护。
*/
@Override
public NutMap exportMineApplyForm(Long applyId) {
MutualApply apply = applyDetail(applyId);
assertMineApplyOwner(apply);
Map<String, Object> docData = buildMineApplyFormDocData(apply);
String fileName = StrUtil.blankToDefault(apply.getUserName(), "申请人") + "大病医疗互助金申请表.docx";
try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {
XWPFTemplate.compile(sysOfficeTemplateUtil.getTemplate(APPLY_FORM_TEMPLATE_CODE))
.render(docData)
.writeAndClose(byteArrayOutputStream);
return NutMap.NEW()
.addv("fileName", fileName)
.addv("bytes", byteArrayOutputStream.toByteArray());
} catch (IOException e) {
throw new BaseException("导出大病医疗互助金申请表失败");
}
}
/**
* 查询审核列表;支持工号/姓名关键字、疾病类型和审核状态组合筛选。
*/
@Override
public Pagination<MutualApply> auditPage(PageForm pageForm, String keyword, String diseaseName, Integer status) {
Cnd cnd = Cnd.NEW();
if (StrUtil.isNotBlank(keyword)) {
String keywordText = "%" + StrUtil.trim(keyword) + "%";
cnd.and(Cnd.exps("loginName", "like", keywordText).or("userName", "like", keywordText));
}
appendDiseaseName(cnd, diseaseName);
if (status != null) {
cnd.and("status", "=", status);
}
return listPage(pageForm.getPageNumber(), pageForm.getPageSize(), cnd.asc("status").desc("submitTime"));
}
/**
* 查询审核详情,自动补齐基金会员入会信息,并按本年度启用配置生成补助预览明细。
*/
@Override
public NutMap auditDetail(Long applyId) {
MutualApply apply = fetch(applyId);
if (apply == null) {
throw new BaseException("申请记录不存在");
}
MutualConfig config = fetchEnabledConfig(getApplyYear(apply));
enrichFundMemberJoinInfo(apply);
fillAuditCalcResult(apply, config);
List<MutualApplySubsidy> subsidies = dao().query(MutualApplySubsidy.class, Cnd.where("applyId", "=", applyId).asc("sort"));
if (subsidies.isEmpty()) {
subsidies = buildSubsidyPreview(apply, config);
}
return NutMap.NEW()
.addv("apply", apply)
.addv("subsidies", subsidies)
.addv("config", config)
.addv("yearUsedAmount", apply.getYearUsedAmount())
.addv("yearRemainAmount", apply.getYearRemainAmount())
.addv("yearLimitAmount", apply.getLimitAmount());
}
/**
* 审核通过时保存主表汇总结果,并重新落库按本年度启用配置计算出的动态区间补助明细。
*/
@Override
public NutMap approve(Long applyId, String bonusRatio, String actualSubsidyAmount, String remark) {
MutualApply apply = fetch(applyId);
if (apply == null) {
throw new BaseException("申请记录不存在");
}
if (apply.getStatus() == null || apply.getStatus() != 0) {
throw new BaseException("只有未审核申请才能审核");
}
MutualConfig config = fetchEnabledConfig(getApplyYear(apply));
enrichFundMemberJoinInfo(apply);
List<MutualApplySubsidy> subsidies = buildSubsidyPreview(apply, config);
BigDecimal total = subsidies.stream()
.map(subsidy -> toBigDecimal(subsidy.getSubsidyAmount()))
.reduce(BigDecimal.ZERO, BigDecimal::add);
fillAuditCalcResult(apply, config, total);
apply.setStatus(1);
apply.setAuditBy(getLoginId());
apply.setAuditTime(new Date());
apply.setAuditOpinion(remark);
apply.setBonusRatio(parseMoney(bonusRatio, apply.getBonusRatio()));
apply.setActualSubsidyAmount(toDouble(toBigDecimal(parseMoney(actualSubsidyAmount, apply.getActualSubsidyAmount()))
.min(toBigDecimal(apply.getActualSubsidyAmount())).setScale(2, RoundingMode.DOWN)));
apply.setRemark(remark);
fillOperator(apply);
insertOrUpdate(apply);
dao().clear(MutualApplySubsidy.class, Cnd.where("applyId", "=", applyId));
for (MutualApplySubsidy subsidy : subsidies) {
subsidy.setApplyId(applyId);
dao().insert(subsidy);
}
return auditDetail(applyId);
}
/**
* 弃审已审核申请:恢复未审核状态,并删除已落库的动态补助明细,避免汇总页继续统计该申请。
*/
@Override
@Aop(TransAop.READ_COMMITTED)
public MutualApply cancelAudit(Long applyId) {
MutualApply apply = fetch(applyId);
if (apply == null) {
throw new BaseException("申请记录不存在");
}
if (apply.getStatus() == null || apply.getStatus() != 1) {
throw new BaseException("只有已审核申请才能弃审");
}
apply.setStatus(0);
apply.setAuditBy(null);
apply.setAuditTime(null);
apply.setAuditOpinion(null);
apply.setConfigId(null);
apply.setConfigYear(null);
apply.setSubsidyTotalAmount(null);
apply.setBonusRatio(null);
apply.setActualSubsidyAmount(null);
apply.setContinuousUnapplyYear(null);
apply.setLimitAmount(null);
apply.setRemark(null);
fillOperator(apply);
dao().update(MutualApply.class, Chain.make("status", 0)
.add("auditBy", null)
.add("auditTime", null)
.add("auditOpinion", null)
.add("configId", null)
.add("configYear", null)
.add("subsidyTotalAmount", null)
.add("bonusRatio", null)
.add("actualSubsidyAmount", null)
.add("continuousUnapplyYear", null)
.add("limitAmount", null)
.add("remark", null)
.add("updateBy", apply.getUpdateBy())
.add("updateTime", new Date()), Cnd.where("id", "=", applyId));
dao().clear(MutualApplySubsidy.class, Cnd.where("applyId", "=", applyId));
return fetch(applyId);
}
/**
* 作废未审核申请:状态置为作废,并把审核人和审核时间作为本次作废操作记录。
*/
@Override
@Aop(TransAop.READ_COMMITTED)
public MutualApply discard(Long applyId) {
MutualApply apply = fetch(applyId);
if (apply == null) {
throw new BaseException("申请记录不存在");
}
if (apply.getStatus() == null || apply.getStatus() != 0) {
throw new BaseException("只有未审核申请才能作废");
}
String loginId = getLoginId();
Date now = new Date();
dao().update(MutualApply.class, Chain.make("status", 2)
.add("auditBy", loginId)
.add("auditTime", now)
.add("updateBy", loginId)
.add("updateTime", now), Cnd.where("id", "=", applyId));
return fetch(applyId);
}
/**
* 取消作废申请:状态恢复为未审核,并清空作废时记录的审核人和审核时间。
*/
@Override
@Aop(TransAop.READ_COMMITTED)
public MutualApply cancelDiscard(Long applyId) {
MutualApply apply = fetch(applyId);
if (apply == null) {
throw new BaseException("申请记录不存在");
}
if (apply.getStatus() == null || apply.getStatus() != 2) {
throw new BaseException("只有作废申请才能取消作废");
}
String loginId = getLoginId();
dao().update(MutualApply.class, Chain.make("status", 0)
.add("auditBy", null)
.add("auditTime", null)
.add("updateBy", loginId)
.add("updateTime", new Date()), Cnd.where("id", "=", applyId));
return fetch(applyId);
}
/**
* 汇总页先返回审核通过的申请数据,并携带动态补助明细列表,前端按明细动态展示列。
*/
@Override
public Pagination<NutMap> summaryPage(PageForm pageForm, String deptName, String userName, Integer joinYear) {
Cnd cnd = buildSummaryCnd(deptName, userName, joinYear);
Pagination<MutualApply> page = listPage(pageForm.getPageNumber(), pageForm.getPageSize(), cnd.desc("auditTime"));
List<NutMap> rows = page.getList().stream()
.map(this::buildSummaryRow)
.toList();
return new Pagination<>(page.getPageNo(), page.getPageSize(), page.getTotalCount(), rows);
}
/**
* 按汇总页当前展示列导出 Excel,动态补助区间列由当前查询结果中的补助明细生成。
*/
@Override
public Workbook exportSummary(String deptName, String userName, Integer joinYear) {
List<MutualApply> applies = query(buildSummaryCnd(deptName, userName, joinYear).desc("auditTime"));
List<String> rangeNames = collectSummaryRangeNames(applies);
List<ExcelExportEntity> exportEntities = buildSummaryExportEntities(rangeNames);
List<Map<String, Object>> exportRows = buildSummaryExportRows(applies, rangeNames);
ExportParams exportParams = new ExportParams();
exportParams.setType(ExcelType.XSSF);
exportParams.setSheetName("医疗互助汇总");
Workbook workbook = ExcelExportUtil.exportExcel(exportParams, exportEntities, exportRows);
rebuildSummaryExportHeader(workbook, exportEntities.size(), rangeNames.size());
return workbook;
}
/**
* 按本年度启用配置生成动态区间预览明细,区间数完全来源于配置,不在代码中固定四段金额。
*/
private List<MutualApplySubsidy> buildSubsidyPreview(MutualApply apply, MutualConfig config) {
if (config == null) {
return List.of();
}
BigDecimal selfPayAmount = toBigDecimal(apply.getSelfPayAmount());
List<MutualRatio> ratios = dao().query(MutualRatio.class, Cnd.where("configId", "=", config.getId()).asc("sort").asc("startAmount"));
return ratios.stream().map(ratio -> buildSubsidyRow(apply, ratio, selfPayAmount)).toList();
}
/**
* 按单个比例区间计算区间参与金额和区间补助金额,金额只取自费金额覆盖到该区间的有效部分。
*/
private MutualApplySubsidy buildSubsidyRow(MutualApply apply, MutualRatio ratio, BigDecimal selfPayAmount) {
BigDecimal start = nvl(ratio.getStartAmount());
BigDecimal end = ratio.getEndAmount();
BigDecimal upper = end == null ? selfPayAmount : selfPayAmount.min(end);
BigDecimal baseAmount = upper.subtract(start).max(BigDecimal.ZERO);
BigDecimal subsidyAmount = baseAmount.multiply(nvl(ratio.getRatio()))
.divide(new BigDecimal("100"), 2, RoundingMode.DOWN);
MutualApplySubsidy row = new MutualApplySubsidy();
row.setApplyId(apply.getId());
row.setRatioId(ratio.getId());
row.setRangeName(buildRangeName(ratio));
row.setStartAmount(toDouble(start));
row.setEndAmount(toDouble(end));
row.setRatio(toDouble(ratio.getRatio()));
row.setBaseAmount(toDouble(baseAmount));
row.setSubsidyAmount(toDouble(subsidyAmount));
row.setSort(ratio.getSort());
return row;
}
/**
* 获取本年度启用互助金配置,审核计算必须使用启用配置,避免误用停用草稿配置。
*/
private MutualConfig fetchEnabledConfig(Integer year) {
MutualConfig config = dao().fetch(MutualConfig.class, Cnd.where("year", "=", year).and("status", "=", 1));
if (config == null) {
throw new BaseException(year + "年度未配置启用的医疗互助补助配置");
}
return config;
}
/**
* 审核计算按申请的申报年度取配置;没有提交时间时使用创建时间兜底。
*/
private Integer getApplyYear(MutualApply apply) {
if (apply != null && apply.getSubmitTime() != null) {
return DateUtil.year(apply.getSubmitTime());
}
if (apply != null && apply.getCreateTime() != null) {
return DateUtil.year(apply.getCreateTime());
}
return DateUtil.thisYear();
}
/**
* 依据基金会员变更记录补齐入会日期和入会年度,来源与 /platform/fundMember/query 查询页保持一致。
*/
private void enrichFundMemberJoinInfo(MutualApply apply) {
if (apply == null || StrUtil.isBlank(apply.getLoginName())) {
return;
}
FundMemberChangeRecord record = dao().fetch(FundMemberChangeRecord.class,
Cnd.where("loginName", "=", apply.getLoginName()).and("isJoin", "=", true).desc("changeTime"));
if (record != null && record.getChangeTime() != null) {
apply.setJoinDate(record.getChangeTime());
apply.setJoinYear(DateUtil.year(record.getChangeTime()));
}
}
/**
* 填充审核计算结果:补助小计、连续未申请年数、奖励系数和最终实际补助金额。
*/
private void fillAuditCalcResult(MutualApply apply, MutualConfig config) {
List<MutualApplySubsidy> subsidies = buildSubsidyPreview(apply, config);
BigDecimal subtotal = subsidies.stream()
.map(subsidy -> toBigDecimal(subsidy.getSubsidyAmount()))
.reduce(BigDecimal.ZERO, BigDecimal::add);
fillAuditCalcResult(apply, config, subtotal);
}
/**
* 根据补助小计和连续未申请奖励比例计算最终补助金额,历史已有申请时连续未申请年数按 1 处理。
*/
private void fillAuditCalcResult(MutualApply apply, MutualConfig config, BigDecimal subtotal) {
int continuousYear = calcContinuousUnapplyYear(apply);
BigDecimal increaseRatio = findIncreaseRatio(config, continuousYear);
BigDecimal bonusRatio = BigDecimal.ONE.add(increaseRatio.divide(new BigDecimal("100"), 4, RoundingMode.HALF_UP));
BigDecimal actualAmount = nvl(subtotal).multiply(bonusRatio).setScale(2, RoundingMode.DOWN);
YearDiseaseLimitCalc limitCalc = calcYearDiseaseLimit(apply, config);
BigDecimal cappedActualAmount = actualAmount.min(limitCalc.remainAmount).max(BigDecimal.ZERO).setScale(2, RoundingMode.DOWN);
apply.setConfigId(config.getId());
apply.setConfigYear(config.getYear());
apply.setSubsidyTotalAmount(toDouble(nvl(subtotal).setScale(2, RoundingMode.DOWN)));
apply.setContinuousUnapplyYear(continuousYear);
apply.setBonusRatio(toDouble(bonusRatio));
apply.setActualSubsidyAmount(toDouble(cappedActualAmount));
apply.setLimitAmount(toDouble(limitCalc.limitAmount));
apply.setYearUsedAmount(toDouble(limitCalc.usedAmount));
apply.setYearRemainAmount(toDouble(limitCalc.remainAmount));
}
/**
* 计算同一申报人同一申报年度同一病种的年度剩余额度,实际补助金额不得超过该额度。
*/
private YearDiseaseLimitCalc calcYearDiseaseLimit(MutualApply apply, MutualConfig config) {
BigDecimal limitAmount = isFirstApplyYear(apply)
? nvl(config.getFirstYearMaxAmount())
: nvl(config.getOtherYearMaxAmount());
BigDecimal usedAmount = sumApprovedSameDiseaseAmount(apply);
BigDecimal remainAmount = limitAmount.subtract(usedAmount).max(BigDecimal.ZERO).setScale(2, RoundingMode.DOWN);
return new YearDiseaseLimitCalc(limitAmount.setScale(2, RoundingMode.DOWN), usedAmount.setScale(2, RoundingMode.DOWN), remainAmount);
}
/**
* 判断当前申报年度是否为该申报人的第一申报年度,当前年度之前无已审核申请即视为第一年度。
*/
private boolean isFirstApplyYear(MutualApply apply) {
if (apply == null || StrUtil.isBlank(apply.getLoginName())) {
return true;
}
Date applyYearStart = getApplyYearStart(apply);
Cnd cnd = Cnd.where("loginName", "=", apply.getLoginName())
.and("status", "=", 1)
.and("submitTime", "<", applyYearStart);
if (apply.getId() != null) {
cnd.and("id", "!=", apply.getId());
}
return dao().count(MutualApply.class, cnd) == 0;
}
/**
* 汇总当前申报年度同申报人同病种已审核实际补助金额,排除当前正在审核的申请。
*/
private BigDecimal sumApprovedSameDiseaseAmount(MutualApply apply) {
if (apply == null || StrUtil.isBlank(apply.getLoginName()) || StrUtil.isBlank(apply.getDiseaseName())) {
return BigDecimal.ZERO;
}
Date applyYearStart = getApplyYearStart(apply);
Date nextYearStart = DateUtil.offsetMonth(applyYearStart, 12);
Cnd cnd = Cnd.where("loginName", "=", apply.getLoginName())
.and("diseaseName", "=", apply.getDiseaseName())
.and("status", "=", 1)
.and("submitTime", ">=", applyYearStart)
.and("submitTime", "<", nextYearStart);
if (apply.getId() != null) {
cnd.and("id", "!=", apply.getId());
}
return dao().query(MutualApply.class, cnd).stream()
.map(item -> toBigDecimal(item.getActualSubsidyAmount()))
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
/**
* 获取当前申请申报年度第一天,年度来源与审核配置年度一致。
*/
private Date getApplyYearStart(MutualApply apply) {
return DateUtil.parseDate(getApplyYear(apply) + "-01-01");
}
/**
* 年度同病种限额计算结果,集中携带上限、已使用和剩余额度。
*/
private static class YearDiseaseLimitCalc {
private final BigDecimal limitAmount;
private final BigDecimal usedAmount;
private final BigDecimal remainAmount;
private YearDiseaseLimitCalc(BigDecimal limitAmount, BigDecimal usedAmount, BigDecimal remainAmount) {
this.limitAmount = limitAmount;
this.usedAmount = usedAmount;
this.remainAmount = remainAmount;
}
}
/**
* 连续未申请年数从基金会员入会年度开始计算;只要存在历史申请记录,就按 1 年奖励口径处理。
*/
private int calcContinuousUnapplyYear(MutualApply apply) {
if (apply == null || StrUtil.isBlank(apply.getLoginName())) {
return 1;
}
int historyCount = dao().count(MutualApply.class,
Cnd.where("loginName", "=", apply.getLoginName()).and("id", "!=", apply.getId()));
if (historyCount > 0) {
return 1;
}
Integer joinYear = apply.getJoinYear();
if (joinYear == null) {
return 1;
}
return Math.max(getApplyYear(apply) - joinYear, 1);
}
/**
* 奖励比例按“达到的连续未申请年数”匹配最高一档,未匹配时默认无奖励。
*/
private BigDecimal findIncreaseRatio(MutualConfig config, int continuousYear) {
MutualBonus bonus = dao().fetch(MutualBonus.class, Cnd.where("configId", "=", config.getId())
.and("continuousYear", "<=", continuousYear).desc("continuousYear"));
return bonus == null || bonus.getIncreaseRatio() == null ? BigDecimal.ZERO : bonus.getIncreaseRatio();
}
/**
* 构建汇总页单行数据,保留申请主信息并附带动态区间明细。
*/
private NutMap buildSummaryRow(MutualApply apply) {
List<MutualApplySubsidy> subsidies = dao().query(MutualApplySubsidy.class, Cnd.where("applyId", "=", apply.getId()).asc("sort"));
return NutMap.NEW()
.addv("apply", apply)
.addv("subsidies", subsidies);
}
/**
* 构建汇总查询条件,列表和导出共用该条件,保证页面展示与 Excel 导出数据范围一致。
*/
private Cnd buildSummaryCnd(String deptName, String userName, Integer joinYear) {
Cnd cnd = Cnd.where("status", "=", 1);
if (StrUtil.isNotBlank(deptName)) {
cnd.and(Cnd.likeEX("deptName", StrUtil.trim(deptName)));
}
if (StrUtil.isNotBlank(userName)) {
cnd.and(Cnd.likeEX("userName", StrUtil.trim(userName)));
}
if (joinYear != null) {
cnd.and("joinYear", "=", joinYear);
}
return cnd;
}
/**
* 收集导出数据中出现过的补助区间名称,顺序按明细排序和首次出现顺序保持稳定。
*/
private List<String> collectSummaryRangeNames(List<MutualApply> applies) {
List<String> rangeNames = new ArrayList<>();
for (MutualApply apply : applies) {
List<MutualApplySubsidy> subsidies = dao().query(MutualApplySubsidy.class, Cnd.where("applyId", "=", apply.getId()).asc("sort"));
for (MutualApplySubsidy subsidy : subsidies) {
if (StrUtil.isNotBlank(subsidy.getRangeName()) && !rangeNames.contains(subsidy.getRangeName())) {
rangeNames.add(subsidy.getRangeName());
}
}
}
return rangeNames;
}
/**
* 构建汇总导出列,基础列和页面一致,补助计算方法下的区间列按查询结果动态生成。
*/
private List<ExcelExportEntity> buildSummaryExportEntities(List<String> rangeNames) {
List<ExcelExportEntity> exportEntities = new ArrayList<>();
exportEntities.add(buildSummaryExportEntity("序号", "index", 10));
exportEntities.add(buildSummaryExportEntity("所在单位", "deptName", 20));
exportEntities.add(buildSummaryExportEntity("姓名", "userName", 14));
exportEntities.add(buildSummaryExportEntity("加入\n时间\n(年)", "joinYear", 14));
exportEntities.add(buildSummaryExportEntity("大病名称\n及病情", "diseaseText", 30));
exportEntities.add(buildSummaryExportEntity("符合规定发\n票金额\n(元)", "medicalTotalAmount", 20));
for (String rangeName : rangeNames) {
exportEntities.add(buildSummaryExportEntity(rangeName, rangeName, 18));
}
exportEntities.add(buildSummaryExportEntity("补助小计", "subsidyTotalAmount", 16));
exportEntities.add(buildSummaryExportEntity("补助\n比例", "bonusRatio", 12));
exportEntities.add(buildSummaryExportEntity("实际补助金\n额(元)", "actualSubsidyAmount", 18));
exportEntities.add(buildSummaryExportEntity("备注", "remark", 20));
return exportEntities;
}
/**
* 创建汇总导出列,统一开启换行以贴合纸质汇总表的多行表头样式。
*/
private ExcelExportEntity buildSummaryExportEntity(String name, Object key, int width) {
ExcelExportEntity entity = new ExcelExportEntity(name, key, width);
entity.setWrap(true);
return entity;
}
/**
* 重建医疗互助汇总导出表头:保留标题和两级表头,省略示例图中的第二行日期。
*/
private void rebuildSummaryExportHeader(Workbook workbook, int totalColumnCount, int rangeColumnCount) {
if (workbook == null || totalColumnCount <= 0 || workbook.getNumberOfSheets() == 0) {
return;
}
Sheet sheet = workbook.getSheetAt(0);
int lastRowNum = sheet.getLastRowNum();
if (lastRowNum >= 0) {
sheet.shiftRows(0, lastRowNum, 2, true, false);
}
Row titleRow = getOrCreateRow(sheet, 0);
Cell titleCell = getOrCreateCell(titleRow, 0);
titleCell.setCellValue(DateUtil.thisYear() + "年大病互助人员申请补助金情况汇总表");
titleCell.setCellStyle(buildSummaryTitleStyle(workbook));
mergeSummaryRegion(sheet, 0, 0, 0, totalColumnCount - 1);
int groupStartColumn = 6;
int groupEndColumn = groupStartColumn + rangeColumnCount;
setMergedHeaderCell(sheet, 1, 2, 0, 0, "序号");
setMergedHeaderCell(sheet, 1, 2, 1, 1, "所在单位");
setMergedHeaderCell(sheet, 1, 2, 2, 2, "姓名");
setMergedHeaderCell(sheet, 1, 2, 3, 3, "加入\n时间\n(年)");
setMergedHeaderCell(sheet, 1, 2, 4, 4, "大病名称\n及病情");
setMergedHeaderCell(sheet, 1, 2, 5, 5, "符合规定发\n票金额\n(元)");
setSummaryGroupHeaderCell(sheet, workbook, groupStartColumn, groupEndColumn);
setMergedHeaderCell(sheet, 1, 2, groupEndColumn + 1, groupEndColumn + 1, "补助\n比例");
setMergedHeaderCell(sheet, 1, 2, groupEndColumn + 2, groupEndColumn + 2, "实际补助金\n额(元)");
setMergedHeaderCell(sheet, 1, 2, groupEndColumn + 3, groupEndColumn + 3, "备注");
}
/**
* 设置合并表头单元格;此处不设置 CellStyle,规避当前运行环境 POI 版本冲突导致的样式方法错误。
*/
private void setMergedHeaderCell(Sheet sheet, int firstRow, int lastRow, int firstCol, int lastCol, String value) {
Cell cell = getOrCreateCell(getOrCreateRow(sheet, firstRow), firstCol);
cell.setCellValue(value);
mergeSummaryRegion(sheet, firstRow, lastRow, firstCol, lastCol);
}
/**
* 设置补助计算方法分组表头样式,仅放大字号并居中,不加粗也不设置填充色和边框。
*/
private void setSummaryGroupHeaderCell(Sheet sheet, Workbook workbook, int firstCol, int lastCol) {
Cell cell = getOrCreateCell(getOrCreateRow(sheet, 1), firstCol);
cell.setCellValue("补助计算方法");
cell.setCellStyle(buildSummaryGroupHeaderStyle(workbook));
mergeSummaryRegion(sheet, 1, 1, firstCol, lastCol);
}
/**
* 创建补助计算方法合并表头样式,按用户要求只做字号放大和居中,不设置加粗。
*/
private CellStyle buildSummaryGroupHeaderStyle(Workbook workbook) {
CellStyle style = workbook.createCellStyle();
style.setAlignment(HorizontalAlignment.CENTER);
style.setVerticalAlignment(VerticalAlignment.CENTER);
Font font = workbook.createFont();
font.setFontHeightInPoints((short) 12);
style.setFont(font);
return style;
}
/**
* 创建汇总导出第一行标题样式,仅设置字体和对齐,避免使用填充色等容易触发 POI 版本冲突的样式 API。
*/
private CellStyle buildSummaryTitleStyle(Workbook workbook) {
CellStyle style = workbook.createCellStyle();
style.setAlignment(HorizontalAlignment.CENTER);
style.setVerticalAlignment(VerticalAlignment.CENTER);
Font font = workbook.createFont();
font.setBold(true);
font.setFontHeightInPoints((short) 18);
style.setFont(font);
return style;
}
/**
* 合并区域只有跨度超过一个单元格时才执行,兼容没有动态区间时只剩补助小计列的场景。
*/
private void mergeSummaryRegion(Sheet sheet, int firstRow, int lastRow, int firstCol, int lastCol) {
if (firstRow == lastRow && firstCol == lastCol) {
return;
}
sheet.addMergedRegion(new CellRangeAddress(firstRow, lastRow, firstCol, lastCol));
}
/**
* 获取或创建指定行,避免 POI 移动行后出现空行导致空指针。
*/
private Row getOrCreateRow(Sheet sheet, int rowIndex) {
Row row = sheet.getRow(rowIndex);
return row == null ? sheet.createRow(rowIndex) : row;
}
/**
* 获取或创建指定单元格,表头重建时用于补齐合并区域内的样式。
*/
private Cell getOrCreateCell(Row row, int columnIndex) {
Cell cell = row.getCell(columnIndex);
return cell == null ? row.createCell(columnIndex) : cell;
}
/**
* 组装导出行数据,动态区间列使用区间名称作为 Map key,与动态表头一一对应。
*/
private List<Map<String, Object>> buildSummaryExportRows(List<MutualApply> applies, List<String> rangeNames) {
List<Map<String, Object>> rows = new ArrayList<>();
for (int i = 0; i < applies.size(); i++) {
MutualApply apply = applies.get(i);
Map<String, Object> row = new LinkedHashMap<>();
row.put("index", i + 1);
row.put("deptName", apply.getDeptName());
row.put("userName", apply.getUserName());
row.put("joinYear", apply.getJoinYear());
row.put("diseaseText", apply.getDiseaseName());
row.put("medicalTotalAmount", formatExportMoney(apply.getMedicalTotalAmount()));
for (String rangeName : rangeNames) {
row.put(rangeName, "");
}
List<MutualApplySubsidy> subsidies = dao().query(MutualApplySubsidy.class, Cnd.where("applyId", "=", apply.getId()).asc("sort"));
for (MutualApplySubsidy subsidy : subsidies) {
row.put(subsidy.getRangeName(), formatExportMoney(subsidy.getSubsidyAmount()));
}
row.put("subsidyTotalAmount", formatExportMoney(apply.getSubsidyTotalAmount()));
row.put("bonusRatio", apply.getBonusRatio());
row.put("actualSubsidyAmount", formatExportMoney(apply.getActualSubsidyAmount()));
row.put("remark", apply.getRemark());
rows.add(row);
}
return rows;
}
/**
* 填充申请保存默认值,提交时状态仍为未审核。
*/
/**
* 组装申请单 Word 模板参数,模板字段名和 /platform/sys/officeTemplate 中维护的 Word 占位符一一对应。
*/
private Map<String, Object> buildMineApplyFormDocData(MutualApply apply) {
Date signDate = firstNotNullDate(apply.getSignTime(), apply.getSubmitTime(), apply.getCreateTime());
Map<String, Object> docData = new LinkedHashMap<>();
docData.put("applyNo", emptyToBlank(apply.getApplyNo()));
docData.put("userId", emptyToBlank(apply.getUserId()));
docData.put("loginName", emptyToBlank(apply.getLoginName()));
docData.put("userName", emptyToBlank(apply.getUserName()));
docData.put("sex", emptyToBlank(apply.getSex()));
docData.put("birthday", formatDate(apply.getBirthday(), "yyyy-MM-dd"));
docData.put("nativePlace", emptyToBlank(apply.getNativePlace()));
docData.put("deptName", emptyToBlank(apply.getDeptName()));
docData.put("education", emptyToBlank(apply.getEducation()));
docData.put("professionalTitle", emptyToBlank(apply.getProfessionalTitle()));
docData.put("idCard", emptyToBlank(apply.getIdCard()));
docData.put("mobile", emptyToBlank(apply.getMobile()));
docData.put("bankCardNo", emptyToBlank(apply.getBankCardNo()));
docData.put("bankName", emptyToBlank(apply.getBankName()));
docData.put("diseaseName", emptyToBlank(apply.getDiseaseName()));
docData.put("hospitalDesc", buildApplyHospitalText(apply));
docData.put("medicalTotalAmount", formatExportMoney(apply.getMedicalTotalAmount()));
docData.put("selfPayAmount", formatExportMoney(apply.getSelfPayAmount()));
docData.put("submitTime", formatDate(apply.getSubmitTime(), "yyyy-MM-dd"));
docData.put("signDate", formatDate(signDate, "yyyy-MM-dd"));
docData.put("signYear", formatDate(signDate, "yyyy"));
docData.put("signMonth", formatDate(signDate, "MM"));
docData.put("signDay", formatDate(signDate, "dd"));
docData.put("remark", emptyToBlank(apply.getRemark()));
docData.put("statusText", buildStatusText(apply.getStatus()));
// 签字图片由 pc-signature 组件保存为文件下载地址,模板中使用 {{@sign}} 渲染图片。
docData.put("sign", StrUtil.isBlank(apply.getSignFileId()) ? null
: sysOfficeTemplateUtil.createPictureRenderData(90, 35, apply.getSignFileId()));
return docData;
}
/**
* 校验“我的申请”导出只能访问当前登录人的申请,避免通过 URL 参数导出他人申请。
*/
private void assertMineApplyOwner(MutualApply apply) {
String loginId = getLoginId();
if (StrUtil.isBlank(loginId) || apply == null) {
return;
}
boolean ownByCreate = StrUtil.equals(loginId, apply.getCreateBy());
boolean ownByUserId = StrUtil.equals(loginId, apply.getUserId());
boolean ownByLoginName = StrUtil.equals(loginId, apply.getLoginName());
if (!ownByCreate && !ownByUserId && !ownByLoginName) {
throw new BaseException("只能导出本人申请单");
}
}
/**
* 将疾病名称和住院说明合并到模板“何时何病伤住何医院”栏,保留原 Word 模板结构。
*/
private String buildApplyHospitalText(MutualApply apply) {
List<String> texts = new ArrayList<>();
if (StrUtil.isNotBlank(apply.getDiseaseName())) {
texts.add("大病名称:" + apply.getDiseaseName());
}
if (StrUtil.isNotBlank(apply.getHospitalDesc())) {
texts.add(apply.getHospitalDesc());
}
return String.join("\n", texts);
}
/**
* 导出状态文案,模板如需展示状态时可直接使用 statusText 参数。
*/
private String buildStatusText(Integer status) {
if (status == null || status == 0) {
return "未审核";
}
if (status == 1) {
return "已审核";
}
if (status == 2) {
return "已作废";
}
return "";
}
private void fillApplyDefaults(MutualApply apply, boolean submit) {
if (StrUtil.isBlank(apply.getApplyNo())) {
apply.setApplyNo("HZ" + DateUtil.format(new Date(), "yyyyMMddHHmmss"));
}
if (apply.getStatus() == null) {
apply.setStatus(0);
}
if (submit && apply.getSubmitTime() == null) {
apply.setSubmitTime(new Date());
apply.setStatus(0);
}
if (apply.getMedicalTotalAmount() == null) {
apply.setMedicalTotalAmount(0D);
}
if (apply.getSelfPayAmount() == null) {
apply.setSelfPayAmount(0D);
}
}
/**
* 补充创建人与更新人,兼容未登录的本地调试场景。
*/
private void fillOperator(MutualApply apply) {
String loginId = getLoginId();
if (apply.getId() == null && StrUtil.isBlank(apply.getCreateBy())) {
apply.setCreateBy(loginId);
}
apply.setUpdateBy(loginId);
}
/**
* 构建当前用户占位信息,真实会员字段映射后续在这里替换。
*/
private NutMap buildCurrentUserPlaceholder() {
String loginId = getLoginId();
return NutMap.NEW()
.addv("loginName", loginId)
.addv("userName", loginId);
}
/**
* 追加大病名称模糊查询条件。
*/
private void appendDiseaseName(Cnd cnd, String diseaseName) {
if (StrUtil.isNotBlank(diseaseName)) {
cnd.and(Cnd.likeEX("diseaseName", StrUtil.trim(diseaseName)));
}
}
/**
* 构建动态区间展示名称。
*/
private String buildRangeName(MutualRatio ratio) {
if (ratio.getEndAmount() == null) {
return formatWan(ratio.getStartAmount()) + "元以上补助" + ratio.getRatio() + "%";
}
return formatWan(ratio.getStartAmount()) + "-" + formatWan(ratio.getEndAmount()) + "元之间补助" + ratio.getRatio() + "%";
}
/**
* 金额标题暂按万元展示,后续可按页面文案继续优化。
*/
private String formatWan(BigDecimal amount) {
if (amount == null) {
return "0";
}
return amount.divide(new BigDecimal("10000"), 0, RoundingMode.DOWN).toPlainString() + "万";
}
/**
* 导出金额与页面展示保持两位小数口径,按业务要求直接截断不四舍五入。
*/
private String formatExportMoney(Double amount) {
return toBigDecimal(amount).setScale(2, RoundingMode.DOWN).toPlainString();
}
/**
* Word 模板日期统一格式化,空日期返回空字符串,避免模板中出现 null。
*/
private String formatDate(Date date, String pattern) {
return date == null ? "" : DateUtil.format(date, pattern);
}
/**
* Word 模板文本空值兜底,保证模板参数渲染为空白而不是 null。
*/
private String emptyToBlank(String value) {
return StrUtil.blankToDefault(value, "");
}
/**
* 按优先级获取第一个非空日期,用于申请人签字日期兜底。
*/
private Date firstNotNullDate(Date... dates) {
for (Date date : dates) {
if (date != null) {
return date;
}
}
return null;
}
/**
* 获取当前登录 ID,未登录时返回空字符串。
*/
private String getLoginId() {
try {
return StpUtil.getLoginIdAsString();
} catch (Exception ignored) {
return "";
}
}
/**
* 空配置金额按 0 处理,配置实体仍保留 BigDecimal 以保证比例和区间计算精度。
*/
private BigDecimal nvl(BigDecimal value) {
return value == null ? BigDecimal.ZERO : value;
}
/**
* 将申请实体中的 Double 金额转换为 BigDecimal,避免直接使用浮点数参与补助公式计算。
*/
private BigDecimal toBigDecimal(Double value) {
return value == null ? BigDecimal.ZERO : BigDecimal.valueOf(value);
}
/**
* 将计算后的 BigDecimal 金额转回实体使用的 Double,页面 JSON 序列化时保持轻量数字类型。
*/
private Double toDouble(BigDecimal value) {
return value == null ? null : value.doubleValue();
}
/**
* 字符串金额转换为实体使用的 Double,空值使用默认金额。
*/
private Double parseMoney(String value, Double defaultValue) {
if (StrUtil.isBlank(value)) {
return defaultValue;
}
return new BigDecimal(value.replace(",", "")).doubleValue();
}
}
@@ -0,0 +1,158 @@
package com.budwk.app.zhgh.staffbenefit.medicalMutualAid.config.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import com.budwk.app.base.result.Result;
import com.budwk.app.zhgh.staffbenefit.medicalMutualAid.config.model.MutualBonus;
import com.budwk.app.zhgh.staffbenefit.medicalMutualAid.config.model.MutualConfig;
import com.budwk.app.zhgh.staffbenefit.medicalMutualAid.config.model.MutualMemberConfig;
import com.budwk.app.zhgh.staffbenefit.medicalMutualAid.config.model.MutualRatio;
import com.budwk.app.zhgh.staffbenefit.medicalMutualAid.config.service.MedicalMutualAidConfigService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
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;
/**
* 医疗互助配置 PC 端控制器,负责页面入口与配置管理接口转发。
*/
@IocBean
@At("/platform/medicalMutualAid/config")
@Api("医疗互助配置")
@Ok("json:full")
public class MedicalMutualAidConfigController {
@Inject
private MedicalMutualAidConfigService medicalMutualAidConfigService;
/**
* 打开医疗互助配置页面。
*/
@At("")
@Ok("beetl:/platform/zhgh/staffbenefit/medicalMutualAid/config/index.html")
@SaCheckPermission("medicalMutualAid.config")
public void index() {
}
/**
* 查询互助金配置列表,供配置列表页按年度和配置名称筛选。
*
* @param year 年度,为空时查询全部年度
* @param configName 配置名称,为空时不限制名称
* @return 互助金配置列表
*/
@At
@ApiOperation("查询医疗互助配置列表")
@SaCheckPermission("medicalMutualAid.config")
public Result listConfigs(Integer year, String configName) {
return Result.success(medicalMutualAidConfigService.listConfigs(year, configName));
}
/**
* 查询指定年度的完整配置数据。
*
* @param year 年度
* @return 聚合配置数据
*/
@At
@ApiOperation("查询医疗互助配置")
@SaCheckPermission("medicalMutualAid.config")
public Result configData(Integer year) {
return Result.success(medicalMutualAidConfigService.getConfigData(year));
}
/**
* 保存互助金基础配置。
*
* @param config 基础配置表单
* @return 保存后的基础配置
*/
@At
@ApiOperation("保存互助金基础配置")
@SaCheckPermission("medicalMutualAid.config")
public Result saveConfig(MutualConfig config) {
return Result.success(medicalMutualAidConfigService.saveConfig(config));
}
/**
* 删除互助金基础配置及其关联配置。
*
* @param id 基础配置 ID
* @return 删除结果
*/
@At
@ApiOperation("删除互助金基础配置")
@SaCheckPermission("medicalMutualAid.config")
public Result deleteConfig(Long id) {
medicalMutualAidConfigService.deleteConfig(id);
return Result.success();
}
/**
* 保存补助比例配置。
*
* @param ratio 补助比例表单
* @return 保存后的补助比例
*/
@At
@ApiOperation("保存补助比例配置")
@SaCheckPermission("medicalMutualAid.config")
public Result saveRatio(MutualRatio ratio) {
return Result.success(medicalMutualAidConfigService.saveRatio(ratio));
}
/**
* 删除补助比例配置。
*
* @param id 补助比例 ID
* @return 删除结果
*/
@At
@ApiOperation("删除补助比例配置")
@SaCheckPermission("medicalMutualAid.config")
public Result deleteRatio(Long id) {
medicalMutualAidConfigService.deleteRatio(id);
return Result.success();
}
/**
* 保存连续未申请奖励配置。
*
* @param bonus 连续未申请奖励表单
* @return 保存后的奖励配置
*/
@At
@ApiOperation("保存连续未申请奖励配置")
@SaCheckPermission("medicalMutualAid.config")
public Result saveBonus(MutualBonus bonus) {
return Result.success(medicalMutualAidConfigService.saveBonus(bonus));
}
/**
* 删除连续未申请奖励配置。
*
* @param id 奖励配置 ID
* @return 删除结果
*/
@At
@ApiOperation("删除连续未申请奖励配置")
@SaCheckPermission("medicalMutualAid.config")
public Result deleteBonus(Long id) {
medicalMutualAidConfigService.deleteBonus(id);
return Result.success();
}
/**
* 保存会员缴费配置。
*
* @param memberConfig 会员缴费配置表单
* @return 保存后的会员缴费配置
*/
@At
@ApiOperation("保存会员缴费配置")
@SaCheckPermission("medicalMutualAid.config")
public Result saveMemberConfig(MutualMemberConfig memberConfig) {
return Result.success(medicalMutualAidConfigService.saveMemberConfig(memberConfig));
}
}
@@ -0,0 +1,48 @@
package com.budwk.app.zhgh.staffbenefit.medicalMutualAid.config.model;
import lombok.Data;
import org.nutz.dao.entity.annotation.ColDefine;
import org.nutz.dao.entity.annotation.ColType;
import org.nutz.dao.entity.annotation.Column;
import org.nutz.dao.entity.annotation.Comment;
import org.nutz.dao.entity.annotation.Id;
import org.nutz.dao.entity.annotation.Table;
import org.nutz.dao.entity.annotation.TableMeta;
import java.math.BigDecimal;
/**
* 连续未申请奖励配置实体,维护连续参保未申请后的补助提升比例。
*/
@Data
@Table("tb_mutual_bonus")
@TableMeta("{'mysql-charset':'utf8mb4'}")
@Comment("连续未申请奖励配置")
public class MutualBonus {
/** 主键,数据库自增生成。 */
@Id
@Comment("主键")
private Long id;
/** 所属互助金基础配置 ID。 */
@Column("config_id")
@Comment("配置ID")
private Long configId;
/** 连续未申请年数。 */
@Column("continuous_year")
@Comment("连续未申请年数")
private Integer continuousYear;
/** 奖励增加比例,单位为百分比。 */
@Column("increase_ratio")
@Comment("增加比例(%)")
private BigDecimal increaseRatio;
/** 奖励说明。 */
@Column("remark")
@ColDefine(type = ColType.VARCHAR, width = 200)
@Comment("备注")
private String remark;
}
@@ -0,0 +1,97 @@
package com.budwk.app.zhgh.staffbenefit.medicalMutualAid.config.model;
import lombok.Data;
import org.nutz.dao.entity.annotation.ColDefine;
import org.nutz.dao.entity.annotation.ColType;
import org.nutz.dao.entity.annotation.Column;
import org.nutz.dao.entity.annotation.Comment;
import org.nutz.dao.entity.annotation.Default;
import org.nutz.dao.entity.annotation.Id;
import org.nutz.dao.entity.annotation.Table;
import org.nutz.dao.entity.annotation.TableMeta;
import org.nutz.dao.interceptor.annotation.PrevInsert;
import org.nutz.dao.interceptor.annotation.PrevUpdate;
import java.math.BigDecimal;
import java.util.Date;
/**
* 互助金补助基础配置实体,映射年度、起补金额、年度封顶金额等主配置。
*/
@Data
@Table("tb_mutual_config")
@TableMeta("{'mysql-charset':'utf8mb4'}")
@Comment("互助金配置")
public class MutualConfig {
/** 主键,数据库自增生成。 */
@Id
@Comment("主键")
private Long id;
/** 配置所属年度,业务上同一年度只能存在一份配置。 */
@Column("year")
@Comment("年度")
private Integer year;
/** 配置名称,用于页面展示和后续业务识别。 */
@Column("config_name")
@ColDefine(type = ColType.VARCHAR, width = 100)
@Comment("配置名称")
private String configName;
/** 起补金额,低于该金额的部分不参与补助计算。 */
@Column("start_amount")
@Default("10000.00")
@Comment("起补金额")
private BigDecimal startAmount;
/** 首年申请最高补助金额。 */
@Column("first_year_max_amount")
@Default("100000.00")
@Comment("第一年最高补助")
private BigDecimal firstYearMaxAmount;
/** 非首年申请最高补助金额。 */
@Column("other_year_max_amount")
@Default("50000.00")
@Comment("其他年度最高补助")
private BigDecimal otherYearMaxAmount;
/** 启停状态:1 启用,0 停用。 */
@Column("status")
@Default("1")
@Comment("状态 1启用 0停用")
private Integer status;
/** 配置备注,用于记录人工说明。 */
@Column("remark")
@ColDefine(type = ColType.VARCHAR, width = 500)
@Comment("备注")
private String remark;
/** 创建人。 */
@Column("create_by")
@ColDefine(type = ColType.VARCHAR, width = 50)
@Comment("创建人")
private String createBy;
/** 创建时间。 */
@Column("create_time")
@PrevInsert(now = true)
@Comment("创建时间")
private Date createTime;
/** 更新人。 */
@Column("update_by")
@ColDefine(type = ColType.VARCHAR, width = 50)
@Comment("更新人")
private String updateBy;
/** 更新时间。 */
@Column("update_time")
@PrevInsert(now = true)
@PrevUpdate(now = true)
@Comment("更新时间")
private Date updateTime;
}
@@ -0,0 +1,35 @@
package com.budwk.app.zhgh.staffbenefit.medicalMutualAid.config.model;
import lombok.Data;
import org.nutz.dao.entity.annotation.Column;
import org.nutz.dao.entity.annotation.Comment;
import org.nutz.dao.entity.annotation.Id;
import org.nutz.dao.entity.annotation.Table;
import org.nutz.dao.entity.annotation.TableMeta;
import java.math.BigDecimal;
/**
* 医疗互助会员缴费配置实体,维护每月缴费金额和连续缴费优惠年限。
*/
@Data
@Table("tb_mutual_member_config")
@TableMeta("{'mysql-charset':'utf8mb4'}")
@Comment("医疗互助会员缴费配置")
public class MutualMemberConfig {
/** 主键,数据库自增生成。 */
@Id
@Comment("主键")
private Long id;
/** 每月缴纳互助金额。 */
@Column("month_amount")
@Comment("每月缴纳互助金额(元)")
private BigDecimal monthAmount;
/** 连续缴费满 N 年后,次年缴费减半。 */
@Column("half_price_year")
@Comment("连续缴费满N年次年缴费减半")
private Integer halfPriceYear;
}
@@ -0,0 +1,58 @@
package com.budwk.app.zhgh.staffbenefit.medicalMutualAid.config.model;
import lombok.Data;
import org.nutz.dao.entity.annotation.ColDefine;
import org.nutz.dao.entity.annotation.ColType;
import org.nutz.dao.entity.annotation.Column;
import org.nutz.dao.entity.annotation.Comment;
import org.nutz.dao.entity.annotation.Id;
import org.nutz.dao.entity.annotation.Table;
import org.nutz.dao.entity.annotation.TableMeta;
import java.math.BigDecimal;
/**
* 互助金补助比例配置实体,按金额区间维护阶梯补助比例。
*/
@Data
@Table("tb_mutual_ratio")
@TableMeta("{'mysql-charset':'utf8mb4'}")
@Comment("互助金补助比例配置")
public class MutualRatio {
/** 主键,数据库自增生成。 */
@Id
@Comment("主键")
private Long id;
/** 所属互助金基础配置 ID。 */
@Column("config_id")
@Comment("配置ID")
private Long configId;
/** 当前补助区间开始金额。 */
@Column("start_amount")
@Comment("开始金额")
private BigDecimal startAmount;
/** 当前补助区间结束金额,空值表示不设上限。 */
@Column("end_amount")
@Comment("结束金额(NULL表示无限)")
private BigDecimal endAmount;
/** 当前区间补助比例,单位为百分比。 */
@Column("ratio")
@Comment("补助比例(%)")
private BigDecimal ratio;
/** 排序号,控制阶梯区间展示与计算顺序。 */
@Column("sort")
@Comment("排序")
private Integer sort;
/** 区间说明。 */
@Column("remark")
@ColDefine(type = ColType.VARCHAR, width = 200)
@Comment("备注")
private String remark;
}
@@ -0,0 +1,109 @@
package com.budwk.app.zhgh.staffbenefit.medicalMutualAid.config.service;
import com.budwk.app.base.service.BaseService;
import com.budwk.app.zhgh.staffbenefit.medicalMutualAid.config.model.MutualBonus;
import com.budwk.app.zhgh.staffbenefit.medicalMutualAid.config.model.MutualConfig;
import com.budwk.app.zhgh.staffbenefit.medicalMutualAid.config.model.MutualMemberConfig;
import com.budwk.app.zhgh.staffbenefit.medicalMutualAid.config.model.MutualRatio;
import org.nutz.lang.util.NutMap;
import java.util.List;
/**
* 医疗互助配置服务,统一处理基础配置、比例配置、奖励配置和会员缴费配置。
*/
public interface MedicalMutualAidConfigService extends BaseService<MutualConfig> {
/**
* 查询指定年度的完整配置数据。
*
* @param year 年度,为空时使用当前年份
* @return 包含基础配置、比例列表、奖励列表和会员缴费配置的聚合数据
*/
NutMap getConfigData(Integer year);
/**
* 查询互助金配置列表,用于配置列表页按年度和配置名称筛选历史配置。
*
* @param year 年度,为空时不限制年度
* @param configName 配置名称,为空时不限制名称
* @return 互助金基础配置列表
*/
List<MutualConfig> listConfigs(Integer year, String configName);
/**
* 保存互助金基础配置。
*
* @param config 互助金基础配置表单
* @return 保存后的互助金基础配置
*/
MutualConfig saveConfig(MutualConfig config);
/**
* 删除互助金基础配置,并同步删除其下比例配置和奖励配置。
*
* @param configId 基础配置 ID
*/
void deleteConfig(Long configId);
/**
* 查询指定基础配置下的补助比例列表。
*
* @param configId 基础配置 ID
* @return 补助比例列表
*/
List<MutualRatio> listRatios(Long configId);
/**
* 保存补助比例配置。
*
* @param ratio 补助比例表单
* @return 保存后的补助比例
*/
MutualRatio saveRatio(MutualRatio ratio);
/**
* 删除补助比例配置。
*
* @param ratioId 补助比例 ID
*/
void deleteRatio(Long ratioId);
/**
* 查询指定基础配置下的连续未申请奖励列表。
*
* @param configId 基础配置 ID
* @return 连续未申请奖励列表
*/
List<MutualBonus> listBonuses(Long configId);
/**
* 保存连续未申请奖励配置。
*
* @param bonus 连续未申请奖励表单
* @return 保存后的连续未申请奖励配置
*/
MutualBonus saveBonus(MutualBonus bonus);
/**
* 删除连续未申请奖励配置。
*
* @param bonusId 连续未申请奖励 ID
*/
void deleteBonus(Long bonusId);
/**
* 查询会员缴费配置,未配置时返回页面默认值。
*
* @return 会员缴费配置
*/
MutualMemberConfig getMemberConfig();
/**
* 保存会员缴费配置。
*
* @param memberConfig 会员缴费配置表单
* @return 保存后的会员缴费配置
*/
MutualMemberConfig saveMemberConfig(MutualMemberConfig memberConfig);
}
@@ -0,0 +1,263 @@
package com.budwk.app.zhgh.staffbenefit.medicalMutualAid.config.service.impl;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.exception.BaseException;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.zhgh.staffbenefit.medicalMutualAid.config.model.MutualBonus;
import com.budwk.app.zhgh.staffbenefit.medicalMutualAid.config.model.MutualConfig;
import com.budwk.app.zhgh.staffbenefit.medicalMutualAid.config.model.MutualMemberConfig;
import com.budwk.app.zhgh.staffbenefit.medicalMutualAid.config.model.MutualRatio;
import com.budwk.app.zhgh.staffbenefit.medicalMutualAid.config.service.MedicalMutualAidConfigService;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.util.NutMap;
import java.math.BigDecimal;
import java.util.List;
/**
* 医疗互助配置服务实现,集中放置配置保存、校验和关联数据维护逻辑。
*/
@IocBean(args = {"refer:dao"})
public class MedicalMutualAidConfigServiceImpl extends BaseServiceImpl<MutualConfig>
implements MedicalMutualAidConfigService {
/**
* 初始化医疗互助配置服务的数据访问对象。
*
* @param dao 数据访问对象
*/
public MedicalMutualAidConfigServiceImpl(Dao dao) {
super(dao);
}
/**
* 聚合返回页面初始化所需的全部配置数据,减少前端重复请求。
*/
@Override
public NutMap getConfigData(Integer year) {
Integer configYear = year == null ? DateUtil.thisYear() : year;
MutualConfig config = fetch(Cnd.where("year", "=", configYear));
if (config == null) {
config = buildDefaultConfig(configYear);
}
Long configId = config.getId();
return NutMap.NEW()
.addv("config", config)
.addv("ratios", configId == null ? List.of() : listRatios(configId))
.addv("bonuses", configId == null ? List.of() : listBonuses(configId))
.addv("memberConfig", getMemberConfig());
}
/**
* 按列表页查询条件检索年度配置,年度和名称均为空时返回全部配置并按年度倒序展示。
*/
@Override
public List<MutualConfig> listConfigs(Integer year, String configName) {
Cnd cnd = Cnd.NEW();
if (year != null) {
cnd.and("year", "=", year);
}
if (StrUtil.isNotBlank(configName)) {
cnd.and("configName", "like", "%" + configName.trim() + "%");
}
return query(cnd.desc("year"));
}
/**
* 保存基础配置前补齐默认值,并处理同一年唯一配置的更新场景。
*/
@Override
public MutualConfig saveConfig(MutualConfig config) {
if (config == null) {
throw new BaseException("配置不能为空");
}
fillDefaultConfig(config);
MutualConfig oldYearConfig = fetch(Cnd.where("year", "=", config.getYear()));
if (oldYearConfig != null && (config.getId() == null || !oldYearConfig.getId().equals(config.getId()))) {
// 同一年只允许一条配置;新增时如果年度已存在,则复用原记录执行更新。
config.setId(oldYearConfig.getId());
config.setCreateBy(oldYearConfig.getCreateBy());
config.setCreateTime(oldYearConfig.getCreateTime());
}
fillOperator(config);
return insertOrUpdate(config);
}
/**
* 删除主配置时同步清理从表,确保比例与奖励配置不会遗留无效 configId。
*/
@Override
public void deleteConfig(Long configId) {
if (configId == null) {
return;
}
dao().clear(MutualRatio.class, Cnd.where("configId", "=", configId));
dao().clear(MutualBonus.class, Cnd.where("configId", "=", configId));
delete(configId);
}
/**
* 按开始金额和排序号查询补助比例,保证页面阶梯区间顺序稳定。
*/
@Override
public List<MutualRatio> listRatios(Long configId) {
if (configId == null) {
return List.of();
}
return dao().query(MutualRatio.class, Cnd.where("configId", "=", configId).asc("sort").asc("startAmount"));
}
/**
* 保存比例前校验所属基础配置存在,并补齐默认排序。
*/
@Override
public MutualRatio saveRatio(MutualRatio ratio) {
if (ratio == null || ratio.getConfigId() == null) {
throw new BaseException("请先保存基础配置");
}
ensureConfigExists(ratio.getConfigId());
if (ratio.getSort() == null) {
ratio.setSort(listRatios(ratio.getConfigId()).size() + 1);
}
return dao().insertOrUpdate(ratio);
}
/**
* 删除指定比例配置,空 ID 直接忽略。
*/
@Override
public void deleteRatio(Long ratioId) {
if (ratioId != null) {
dao().delete(MutualRatio.class, ratioId);
}
}
/**
* 按连续未申请年数查询奖励配置,保证页面展示顺序符合业务递增关系。
*/
@Override
public List<MutualBonus> listBonuses(Long configId) {
if (configId == null) {
return List.of();
}
return dao().query(MutualBonus.class, Cnd.where("configId", "=", configId).asc("continuousYear"));
}
/**
* 保存奖励前校验所属基础配置存在,避免保存到无效主配置下。
*/
@Override
public MutualBonus saveBonus(MutualBonus bonus) {
if (bonus == null || bonus.getConfigId() == null) {
throw new BaseException("请先保存基础配置");
}
ensureConfigExists(bonus.getConfigId());
return dao().insertOrUpdate(bonus);
}
/**
* 删除指定奖励配置,空 ID 直接忽略。
*/
@Override
public void deleteBonus(Long bonusId) {
if (bonusId != null) {
dao().delete(MutualBonus.class, bonusId);
}
}
/**
* 查询会员缴费配置;未配置时返回默认表单,方便页面直接展示。
*/
@Override
public MutualMemberConfig getMemberConfig() {
MutualMemberConfig memberConfig = dao().fetch(MutualMemberConfig.class, Cnd.NEW());
if (memberConfig != null) {
return memberConfig;
}
MutualMemberConfig defaultConfig = new MutualMemberConfig();
defaultConfig.setMonthAmount(new BigDecimal("0.00"));
defaultConfig.setHalfPriceYear(1);
return defaultConfig;
}
/**
* 保存会员缴费配置时复用已有唯一记录,避免产生多条全局缴费配置。
*/
@Override
public MutualMemberConfig saveMemberConfig(MutualMemberConfig memberConfig) {
if (memberConfig == null) {
throw new BaseException("会员缴费配置不能为空");
}
MutualMemberConfig oldConfig = dao().fetch(MutualMemberConfig.class, Cnd.NEW());
if (oldConfig != null && memberConfig.getId() == null) {
memberConfig.setId(oldConfig.getId());
}
return dao().insertOrUpdate(memberConfig);
}
/**
* 构造页面默认基础配置,数据库未保存时也能展示截图中的默认业务值。
*/
private MutualConfig buildDefaultConfig(Integer year) {
MutualConfig config = new MutualConfig();
config.setYear(year);
config.setConfigName(year + "年医疗互助补助配置");
config.setStartAmount(new BigDecimal("10000.00"));
config.setFirstYearMaxAmount(new BigDecimal("100000.00"));
config.setOtherYearMaxAmount(new BigDecimal("50000.00"));
config.setStatus(1);
return config;
}
/**
* 保存前补齐基础配置默认值,避免数据库非空字段因页面未传值保存失败。
*/
private void fillDefaultConfig(MutualConfig config) {
if (config.getYear() == null) {
config.setYear(DateUtil.thisYear());
}
if (StrUtil.isBlank(config.getConfigName())) {
config.setConfigName(config.getYear() + "年医疗互助补助配置");
}
if (config.getStartAmount() == null) {
config.setStartAmount(new BigDecimal("10000.00"));
}
if (config.getFirstYearMaxAmount() == null) {
config.setFirstYearMaxAmount(new BigDecimal("100000.00"));
}
if (config.getOtherYearMaxAmount() == null) {
config.setOtherYearMaxAmount(new BigDecimal("50000.00"));
}
if (config.getStatus() == null) {
config.setStatus(1);
}
}
/**
* 校验基础配置是否存在,比例与奖励保存必须依赖有效主配置。
*/
private void ensureConfigExists(Long configId) {
if (fetch(configId) == null) {
throw new BaseException("基础配置不存在,请刷新后重试");
}
}
/**
* 记录当前登录人,兼容未登录或测试环境下获取不到登录 ID 的情况。
*/
private void fillOperator(MutualConfig config) {
String loginId = "";
try {
loginId = StpUtil.getLoginIdAsString();
} catch (Exception ignored) {
}
if (config.getId() == null && StrUtil.isBlank(config.getCreateBy())) {
config.setCreateBy(loginId);
}
config.setUpdateBy(loginId);
}
}
@@ -0,0 +1,76 @@
package com.budwk.app.zhgh.staffbenefit.medicalMutualAid.medical.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.result.Result;
import com.budwk.app.zhgh.staffbenefit.medicalMutualAid.medical.model.MedicalDiseaseType;
import com.budwk.app.zhgh.staffbenefit.medicalMutualAid.medical.service.MedicalDiseaseTypeService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
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;
/**
* 医疗互助疾病类型 PC 端控制器。
*/
@IocBean
@At("/platform/medicalMutualAid/medical/diseaseType")
@Api("医疗互助疾病类型")
@Ok("json:full")
public class MedicalDiseaseTypeController {
@Inject
private MedicalDiseaseTypeService medicalDiseaseTypeService;
/**
* 打开疾病类型 PC 管理页面。
*/
@At("")
@Ok("beetl:/platform/zhgh/staffbenefit/medicalMutualAid/medical/diseaseType/index.html")
@SaCheckPermission("medicalMutualAid.medical.diseaseType")
public void index() {
}
/**
* 分页查询疾病类型。
*
* @param pageForm 分页及查询参数
* @return 分页结果
*/
@At
@ApiOperation("分页查询疾病类型")
@SaCheckPermission("medicalMutualAid.medical.diseaseType")
public Result pageData(PageForm pageForm) {
return Result.success(medicalDiseaseTypeService.pageData(pageForm));
}
/**
* 保存或修改疾病类型。
*
* @param medicalDiseaseType 疾病类型表单数据
* @return 保存结果
*/
@At
@ApiOperation("保存或修改疾病类型")
@SaCheckPermission("medicalMutualAid.medical.diseaseType")
public Result doSubmit(MedicalDiseaseType medicalDiseaseType) {
medicalDiseaseTypeService.saveDiseaseType(medicalDiseaseType);
return Result.success();
}
/**
* 删除指定疾病类型。
*
* @param id 疾病类型主键
* @return 删除结果
*/
@At
@ApiOperation("删除疾病类型")
@SaCheckPermission("medicalMutualAid.medical.diseaseType")
public Result doDelete(String id) {
medicalDiseaseTypeService.deleteDiseaseType(id);
return Result.success();
}
}
@@ -0,0 +1,76 @@
package com.budwk.app.zhgh.staffbenefit.medicalMutualAid.medical.h5controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.result.Result;
import com.budwk.app.zhgh.staffbenefit.medicalMutualAid.medical.model.MedicalDiseaseType;
import com.budwk.app.zhgh.staffbenefit.medicalMutualAid.medical.service.MedicalDiseaseTypeService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
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;
/**
* 医疗互助疾病类型移动端控制器。
*/
@IocBean
@At("/platform/h5/medicalMutualAid/medical/diseaseType")
@Api("移动端医疗互助疾病类型")
@Ok("json:full")
public class H5MedicalDiseaseTypeController {
@Inject
private MedicalDiseaseTypeService medicalDiseaseTypeService;
/**
* 打开疾病类型移动端管理页面。
*/
@At("")
@Ok("beetl:/platform/zhghh5/staffbenefit/medicalMutualAid/medical/diseaseType/index.html")
@SaCheckPermission("h5.medicalMutualAid.medical.diseaseType")
public void index() {
}
/**
* 移动端分页查询疾病类型。
*
* @param pageForm 分页及查询参数
* @return 分页结果
*/
@At
@ApiOperation("移动端分页查询疾病类型")
@SaCheckPermission("h5.medicalMutualAid.medical.diseaseType")
public Result pageData(PageForm pageForm) {
return Result.success(medicalDiseaseTypeService.pageData(pageForm));
}
/**
* 移动端保存或修改疾病类型。
*
* @param medicalDiseaseType 疾病类型表单数据
* @return 保存结果
*/
@At
@ApiOperation("移动端保存或修改疾病类型")
@SaCheckPermission("h5.medicalMutualAid.medical.diseaseType")
public Result doSubmit(MedicalDiseaseType medicalDiseaseType) {
medicalDiseaseTypeService.saveDiseaseType(medicalDiseaseType);
return Result.success();
}
/**
* 移动端删除指定疾病类型。
*
* @param id 疾病类型主键
* @return 删除结果
*/
@At
@ApiOperation("移动端删除疾病类型")
@SaCheckPermission("h5.medicalMutualAid.medical.diseaseType")
public Result doDelete(String id) {
medicalDiseaseTypeService.deleteDiseaseType(id);
return Result.success();
}
}
@@ -0,0 +1,52 @@
package com.budwk.app.zhgh.staffbenefit.medicalMutualAid.medical.model;
import com.budwk.app.base.model.BaseModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import org.nutz.dao.entity.annotation.ColDefine;
import org.nutz.dao.entity.annotation.ColType;
import org.nutz.dao.entity.annotation.Column;
import org.nutz.dao.entity.annotation.Comment;
import org.nutz.dao.entity.annotation.Default;
import org.nutz.dao.entity.annotation.Name;
import org.nutz.dao.entity.annotation.Table;
import org.nutz.dao.interceptor.annotation.PrevInsert;
/**
* 医疗互助疾病类型实体,用于维护可申请的疾病基础数据。
*/
@Data
@Comment("医疗互助疾病类型表")
@Accessors(chain = true)
@Table("medical_disease_type")
@EqualsAndHashCode(callSuper = true)
public class MedicalDiseaseType extends BaseModel {
/** 疾病类型主键。 */
@Column
@Name
@Comment("ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
@PrevInsert(uu32 = true)
private String id;
/** 疾病名称。 */
@Column
@ColDefine(type = ColType.VARCHAR, width = 50)
@Comment("疾病名称")
private String diseaseName;
/** 疾病编码,用于列表排序和业务引用。 */
@Column
@Comment("疾病编码")
@ColDefine(type = ColType.VARCHAR, width = 20)
private String diseaseCode;
/** 是否属于重大疾病,默认是。 */
@Column
@Comment("是否重大疾病")
@Default("1")
@ColDefine(type = ColType.BOOLEAN)
private Boolean isMajorDiseases;
}
@@ -0,0 +1,36 @@
package com.budwk.app.zhgh.staffbenefit.medicalMutualAid.medical.service;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.service.BaseService;
import com.budwk.app.zhgh.staffbenefit.medicalMutualAid.medical.model.MedicalDiseaseType;
/**
* 医疗互助疾病类型服务,统一承载 PC 端与移动端的公共业务逻辑。
*/
public interface MedicalDiseaseTypeService extends BaseService<MedicalDiseaseType> {
/**
* 按疾病名称分页查询疾病类型。
*
* @param pageForm 分页及关键字参数
* @return 疾病类型分页结果
*/
Pagination<MedicalDiseaseType> pageData(PageForm pageForm);
/**
* 新增或修改疾病类型。
*
* @param medicalDiseaseType 疾病类型表单数据
* @return 保存后的疾病类型
*/
MedicalDiseaseType saveDiseaseType(MedicalDiseaseType medicalDiseaseType);
/**
* 根据主键删除疾病类型。
*
* @param id 疾病类型主键
* @return 删除记录数
*/
int deleteDiseaseType(String id);
}
@@ -0,0 +1,67 @@
package com.budwk.app.zhgh.staffbenefit.medicalMutualAid.medical.service.impl;
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.service.impl.BaseServiceImpl;
import com.budwk.app.zhgh.staffbenefit.medicalMutualAid.medical.model.MedicalDiseaseType;
import com.budwk.app.zhgh.staffbenefit.medicalMutualAid.medical.service.MedicalDiseaseTypeService;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.ioc.loader.annotation.IocBean;
/**
* 医疗互助疾病类型服务实现。
*/
@IocBean(args = {"refer:dao"})
public class MedicalDiseaseTypeServiceImpl extends BaseServiceImpl<MedicalDiseaseType>
implements MedicalDiseaseTypeService {
/**
* 初始化疾病类型服务使用的数据访问对象。
*
* @param dao 数据访问对象
*/
public MedicalDiseaseTypeServiceImpl(Dao dao) {
super(dao);
}
/**
* 构造统一查询条件,确保 PC 端与移动端使用相同的排序和过滤规则。
*/
@Override
public Pagination<MedicalDiseaseType> pageData(PageForm pageForm) {
Cnd cnd = Cnd.NEW();
if (StrUtil.isNotBlank(pageForm.getSearchKeyword())) {
// 关键字只匹配疾病名称,保持与源功能查询口径一致。
cnd.and(Cnd.likeEX("diseaseName", StrUtil.trim(pageForm.getSearchKeyword())));
}
cnd.asc("diseaseCode");
return listPage(pageForm.getPageNumber(), pageForm.getPageSize(), cnd);
}
/**
* 保存前规范文本字段,并补全重大疾病默认值。
*/
@Override
public MedicalDiseaseType saveDiseaseType(MedicalDiseaseType medicalDiseaseType) {
// 去除用户误输入的首尾空格,避免产生看似重复的数据。
medicalDiseaseType.setDiseaseName(StrUtil.trim(medicalDiseaseType.getDiseaseName()));
medicalDiseaseType.setDiseaseCode(StrUtil.trim(medicalDiseaseType.getDiseaseCode()));
if (medicalDiseaseType.getIsMajorDiseases() == null) {
medicalDiseaseType.setIsMajorDiseases(Boolean.TRUE);
}
return insertOrUpdate(medicalDiseaseType);
}
/**
* 校验主键后执行删除,避免空主键触发无意义数据库操作。
*/
@Override
public int deleteDiseaseType(String id) {
if (StrUtil.isBlank(id)) {
return 0;
}
return delete(id);
}
}
@@ -0,0 +1,15 @@
-- 医疗互助疾病类型基础表;脚本可重复执行。
CREATE TABLE IF NOT EXISTS `medical_disease_type` (
`id` varchar(32) NOT NULL COMMENT 'ID',
`diseaseName` varchar(50) NOT NULL COMMENT '疾病名称',
`diseaseCode` varchar(20) NOT NULL COMMENT '疾病编码',
`isMajorDiseases` tinyint(1) NOT NULL DEFAULT 1 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_medical_disease_type_code` (`diseaseCode`),
KEY `idx_medical_disease_type_name` (`diseaseName`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='医疗互助疾病类型表';
@@ -0,0 +1,71 @@
DROP TABLE IF EXISTS `tb_mutual_apply`;
CREATE TABLE `tb_mutual_apply` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`apply_no` varchar(50) DEFAULT NULL COMMENT '申请编号',
`user_id` varchar(50) DEFAULT NULL COMMENT '申请人用户ID',
`login_name` varchar(50) DEFAULT NULL COMMENT '申请人工号/登录名',
`user_name` varchar(50) NOT NULL COMMENT '申请人姓名',
`sex` varchar(20) DEFAULT NULL COMMENT '性别',
`birthday` date DEFAULT NULL COMMENT '出生年月',
`native_place` varchar(100) DEFAULT NULL COMMENT '籍贯',
`education` varchar(50) DEFAULT NULL COMMENT '学历',
`professional_title` varchar(100) DEFAULT NULL COMMENT '职称',
`dept_id` varchar(50) DEFAULT NULL COMMENT '工作部门ID',
`dept_name` varchar(200) DEFAULT NULL COMMENT '工作部门(系部)',
`id_card` varchar(30) DEFAULT NULL COMMENT '身份证号码',
`mobile` varchar(30) DEFAULT NULL COMMENT '联系电话',
`bank_card_no` varchar(50) DEFAULT NULL COMMENT '工资银行卡号',
`bank_name` varchar(200) DEFAULT NULL COMMENT '开户行',
`join_date` date DEFAULT NULL COMMENT '入会时间',
`join_year` int DEFAULT NULL COMMENT '加入年份',
`disease_type_id` varchar(32) DEFAULT NULL COMMENT '大病类型ID',
`disease_name` varchar(200) NOT NULL COMMENT '大病名称',
`hospital_desc` varchar(500) DEFAULT NULL COMMENT '何时何病住何医院',
`medical_total_amount` decimal(12,2) NOT NULL DEFAULT '0.00' COMMENT '全年医疗费用总金额',
`self_pay_amount` decimal(12,2) NOT NULL DEFAULT '0.00' COMMENT '自付金额',
`sign_file_id` varchar(50) DEFAULT NULL COMMENT '申请人签名文件ID',
`sign_time` datetime DEFAULT NULL COMMENT '申请人签名时间',
`status` tinyint NOT NULL DEFAULT 0 COMMENT '状态 0未审核 1已审核',
`submit_time` datetime DEFAULT NULL COMMENT '提交时间',
`audit_by` varchar(50) DEFAULT NULL COMMENT '审核人',
`audit_time` datetime DEFAULT NULL COMMENT '审核时间',
`audit_opinion` varchar(500) DEFAULT NULL COMMENT '审核意见',
`config_id` bigint DEFAULT NULL COMMENT '审核使用的互助金配置ID',
`config_year` int DEFAULT NULL COMMENT '审核使用的配置年度',
`subsidy_total_amount` decimal(12,2) DEFAULT NULL COMMENT '补助小计',
`bonus_ratio` decimal(5,2) DEFAULT NULL COMMENT '奖励/补助系数',
`actual_subsidy_amount` decimal(12,2) DEFAULT NULL COMMENT '实际补助金额',
`continuous_unapply_year` int DEFAULT NULL COMMENT '连续未申请年数',
`limit_amount` decimal(12,2) DEFAULT NULL COMMENT '本次适用最高补助限额',
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
`create_by` varchar(50) DEFAULT NULL COMMENT '创建人',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_by` varchar(50) DEFAULT NULL COMMENT '更新人',
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `idx_user_id` (`user_id`),
KEY `idx_login_name` (`login_name`),
KEY `idx_dept_id` (`dept_id`),
KEY `idx_status` (`status`),
KEY `idx_disease_type_id` (`disease_type_id`),
KEY `idx_join_year` (`join_year`),
KEY `idx_submit_time` (`submit_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='互助金申请表';
DROP TABLE IF EXISTS `tb_mutual_apply_subsidy`;
CREATE TABLE `tb_mutual_apply_subsidy` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
`apply_id` bigint NOT NULL COMMENT '申请ID',
`ratio_id` bigint DEFAULT NULL COMMENT '使用的补助比例配置ID',
`range_name` varchar(100) DEFAULT NULL COMMENT '区间名称',
`start_amount` decimal(12,2) NOT NULL DEFAULT '0.00' COMMENT '区间开始金额',
`end_amount` decimal(12,2) DEFAULT NULL COMMENT '区间结束金额(NULL表示无限)',
`ratio` decimal(5,2) NOT NULL DEFAULT '0.00' COMMENT '补助比例(%)',
`base_amount` decimal(12,2) NOT NULL DEFAULT '0.00' COMMENT '本区间参与计算金额',
`subsidy_amount` decimal(12,2) NOT NULL DEFAULT '0.00' COMMENT '本区间补助金额',
`sort` int DEFAULT 1 COMMENT '排序',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`),
KEY `idx_apply_id` (`apply_id`),
KEY `idx_ratio_id` (`ratio_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='互助金补贴金额明细表';
@@ -0,0 +1,51 @@
-- 医疗互助疾病类型 PC 端与移动端菜单。
-- 使用 permission 做幂等判断,重复执行不会插入重复菜单。
-- PC 端一级菜单。
INSERT INTO sys_menu (id, parentId, path, name, aliasName, type, href, target, icon, showit, disabled, permission, note, location, hasChildren, createdBy, createdAt, updatedBy, updatedAt, delFlag, platform, moduleId, picIcon, initialPinyinName, isRecommendApp, isRecommendService)
SELECT 'e5d7a110aa214cdf8505fd0000000001', '', LPAD(IFNULL(MAX(CAST(path AS UNSIGNED)), 0) + 1, 4, '0'), '医疗互助', 'Medical Mutual Aid', 'menu', '', '', 'ti-heart', 1, 0, 'medicalMutualAid', NULL, 992, 1, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, 'PC', NULL, NULL, 'y', 0, 0
FROM sys_menu
WHERE platform = 'PC' AND (parentId = '' OR parentId IS NULL) AND CHAR_LENGTH(path) = 4
HAVING NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'medicalMutualAid' AND platform = 'PC') t);
-- PC 端医疗管理分组。
INSERT INTO sys_menu (id, parentId, path, name, aliasName, type, href, target, icon, showit, disabled, permission, note, location, hasChildren, createdBy, createdAt, updatedBy, updatedAt, delFlag, platform, moduleId, picIcon, initialPinyinName, isRecommendApp, isRecommendService)
SELECT 'e5d7a110aa214cdf8505fd0000000002', p.id, CONCAT(p.path, '0001'), '医疗管理', 'Medical Manage', 'menu', '', '', '', 1, 0, 'medicalMutualAid.medical', NULL, 1, 1, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, 'PC', NULL, NULL, 'y', 0, 0
FROM sys_menu p
WHERE p.permission = 'medicalMutualAid' AND p.platform = 'PC'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'medicalMutualAid.medical' AND platform = 'PC') t);
-- PC 端疾病种类页面。
INSERT INTO sys_menu (id, parentId, path, name, aliasName, type, href, target, icon, showit, disabled, permission, note, location, hasChildren, createdBy, createdAt, updatedBy, updatedAt, delFlag, platform, moduleId, picIcon, initialPinyinName, isRecommendApp, isRecommendService)
SELECT 'e5d7a110aa214cdf8505fd0000000003', p.id, CONCAT(p.path, '0001'), '疾病种类', 'Disease Type', 'menu', '/platform/medicalMutualAid/medical/diseaseType', 'data-pjax', '', 1, 0, 'medicalMutualAid.medical.diseaseType', NULL, 1, 0, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, 'PC', NULL, NULL, 'j', 0, 0
FROM sys_menu p
WHERE p.permission = 'medicalMutualAid.medical' AND p.platform = 'PC'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'medicalMutualAid.medical.diseaseType' AND platform = 'PC') t);
-- 移动端一级菜单。
INSERT INTO sys_menu (id, parentId, path, name, aliasName, type, href, target, icon, showit, disabled, permission, note, location, hasChildren, createdBy, createdAt, updatedBy, updatedAt, delFlag, platform, moduleId, picIcon, initialPinyinName, isRecommendApp, isRecommendService)
SELECT 'e5d7a110aa214cdf8505fd0000000004', '', LPAD(IFNULL(MAX(CAST(path AS UNSIGNED)), 0) + 1, 4, '0'), '医疗互助', 'Medical Mutual Aid', 'menu', '', '', 'heart-o', 1, 0, 'h5.medicalMutualAid', NULL, 992, 1, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, 'H5', NULL, NULL, 'y', 0, 0
FROM sys_menu
WHERE platform = 'H5' AND (parentId = '' OR parentId IS NULL) AND CHAR_LENGTH(path) = 4
HAVING NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'h5.medicalMutualAid' AND platform = 'H5') t);
-- 移动端疾病种类页面,权限严格使用 h5. 前缀。
INSERT INTO sys_menu (id, parentId, path, name, aliasName, type, href, target, icon, showit, disabled, permission, note, location, hasChildren, createdBy, createdAt, updatedBy, updatedAt, delFlag, platform, moduleId, picIcon, initialPinyinName, isRecommendApp, isRecommendService)
SELECT 'e5d7a110aa214cdf8505fd0000000005', p.id, CONCAT(p.path, '0001'), '疾病种类', 'Disease Type', 'menu', '/platform/h5/medicalMutualAid/medical/diseaseType', 'data-pjax', '', 1, 0, 'h5.medicalMutualAid.medical.diseaseType', NULL, 1, 0, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, 'H5', NULL, NULL, 'j', 0, 0
FROM sys_menu p
WHERE p.permission = 'h5.medicalMutualAid' AND p.platform = 'H5'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'h5.medicalMutualAid.medical.diseaseType' AND platform = 'H5') t);
-- 默认授予系统管理员,其他角色可在角色管理中按需分配。
INSERT INTO sys_role_menu (roleId, menuId)
SELECT r.id, m.id
FROM sys_role r
JOIN sys_menu m ON m.permission IN (
'medicalMutualAid',
'medicalMutualAid.medical',
'medicalMutualAid.medical.diseaseType',
'h5.medicalMutualAid',
'h5.medicalMutualAid.medical.diseaseType'
)
LEFT JOIN sys_role_menu rm ON rm.roleId = r.id AND rm.menuId = m.id
WHERE r.code = 'SYSADMIN' AND rm.roleId IS NULL;
@@ -0,0 +1,221 @@
<!--#
layout("/layouts/platform.html"){
#-->
<style>
.mutual-list-page { padding: 16px 20px; background: #f3f6fa; height: calc(100vh - 88px); box-sizing: border-box; display: flex; flex-direction: column; overflow: hidden; }
.search-panel { background: #fff; padding: 16px 42px; margin-bottom: 12px; box-sizing: border-box; flex-shrink: 0; }
.search-panel .el-form-item { margin-bottom: 0; margin-right: 14px; }
.content-panel { background: #fff; padding: 16px 20px 16px; box-sizing: border-box; flex: 1; min-height: 0; display: flex; flex-direction: column; }
.panel-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; }
.section-title { border-left: 4px solid #0079c2; padding-left: 10px; color: #0079c2; font-size: 16px; font-weight: 700; line-height: 22px; }
.table-actions .el-button { min-width: 54px; padding: 7px 14px; border-radius: 2px; }
.mutual-table.el-table th { background: #fafafa; color: #303133; font-weight: 700; }
.mutual-table.el-table td, .mutual-table.el-table th { padding: 11px 0; }
.pager { display: flex; justify-content: flex-start; align-items: center; padding-top: 12px; flex-shrink: 0; }
.pager .total-text { margin-right: 18px; color: #303133; }
</style>
<div id="app" v-cloak class="mutual-list-page">
<div class="search-panel">
<el-form :inline="true" :model="searchForm">
<el-form-item label="工号/姓名">
<el-input v-model="searchForm.keyword" clearable placeholder="请输入工号或姓名" style="width: 220px"></el-input>
</el-form-item>
<el-form-item label="大病名称">
<el-select v-model="searchForm.diseaseName" clearable filterable placeholder="请选择大病名称" style="width: 220px">
<el-option v-for="item in diseaseTypes" :key="item.id" :label="item.diseaseName" :value="item.diseaseName"></el-option>
</el-select>
</el-form-item>
<el-form-item label="状态">
<el-select v-model="searchForm.status" clearable placeholder="请选择状态" style="width: 160px">
<el-option v-for="item in statusOptions" :key="item.value" :label="item.label" :value="item.value"></el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="loadPage">查询</el-button>
<el-button @click="resetSearch">重置</el-button>
</el-form-item>
</el-form>
</div>
<div class="content-panel" ref="contentPanel">
<div class="panel-header" ref="panelHeader">
<div class="section-title">互助会审核</div>
</div>
<el-table class="mutual-table" :data="tableData" border v-loading="loading" :height="tableHeight" style="width: 100%">
<el-table-column type="index" label="序号" width="70" align="center"></el-table-column>
<el-table-column prop="loginName" label="申请人工号" width="130" align="center"></el-table-column>
<el-table-column prop="userName" label="申请人姓名" width="130" align="center"></el-table-column>
<el-table-column prop="joinDate" label="入会时间" width="130" align="center"></el-table-column>
<el-table-column prop="diseaseName" label="大病名称" min-width="200" align="center"></el-table-column>
<el-table-column label="全年医疗费用总金额(元)" width="190" align="center">
<template slot-scope="{row}">{{ formatMoney(row.medicalTotalAmount) }}</template>
</el-table-column>
<el-table-column label="自费金额(元)" width="150" align="center">
<template slot-scope="{row}">{{ formatMoney(row.selfPayAmount) }}</template>
</el-table-column>
<el-table-column label="状态" width="100" align="center">
<template slot-scope="{row}">
<el-tag v-if="isAudited(row)" type="success" size="mini">已审核</el-tag>
<el-tag v-else-if="isDiscarded(row)" type="danger" size="mini">已作废</el-tag>
<el-tag v-else type="warning" size="mini">未审核</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="260" align="center">
<template slot-scope="{row}">
<div class="table-actions">
<el-button size="mini" @click="goView(row)">查看</el-button>
<el-button v-if="isPending(row)" size="mini" type="primary" @click="goReview(row)">审核</el-button>
<el-button v-if="isAudited(row)" size="mini" type="warning" @click="cancelAudit(row)">弃审</el-button>
<el-button v-if="isPending(row)" size="mini" type="danger" @click="discard(row)">作废</el-button>
<el-button v-if="isDiscarded(row)" size="mini" type="warning" @click="cancelDiscard(row)">取消作废</el-button>
</div>
</template>
</el-table-column>
</el-table>
<div class="pager" ref="pager">
<span class="total-text">共 {{ totalCount }} 条</span>
<el-pagination background layout="sizes, prev, pager, next" :current-page.sync="pageForm.pageNumber" :page-size.sync="pageForm.pageSize" :total="totalCount" @current-change="loadPage" @size-change="loadPage"></el-pagination>
</div>
</div>
</div>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
data() {
return {
loading: false,
tableData: [],
diseaseTypes: [],
statusOptions: [
{ label: "未审核", value: 0 },
{ label: "已审核", value: 1 },
{ label: "作废", value: 2 }
],
totalCount: 0,
tableHeight: 360,
apiBase: "${base!}/platform/medicalMutualAid/apply/audit",
reviewPath: "${base!}/platform/medicalMutualAid/apply/audit/review",
diseaseTypeApi: "${base!}/platform/medicalMutualAid/medical/diseaseType",
searchForm: { keyword: "", diseaseName: "", status: "" },
pageForm: { pageNumber: 1, pageSize: 10 }
}
},
methods: {
// 加载互助疾病类型下拉,查询条件与申请页面共用疾病基础数据。
loadDiseaseTypes() {
this.$axios.post(this.diseaseTypeApi + "/pageData", { pageNumber: 1, pageSize: 999 }).then((resp) => {
if (resp.code === 0) {
this.diseaseTypes = resp.data.list || []
}
})
},
// 查询互助金待审核/已审核申请列表。
loadPage() {
this.loading = true
const params = Object.assign({}, this.pageForm, this.searchForm)
if (params.status === "" || params.status === null || params.status === undefined) {
delete params.status
}
this.$axios.post(this.apiBase + "/page", params).then((resp) => {
if (resp.code === 0) {
this.tableData = resp.data.list || []
this.totalCount = resp.data.totalCount || 0
}
}).finally(() => {
this.loading = false
this.$nextTick(this.updateTableHeight)
})
},
// 重置审核列表查询条件。
resetSearch() {
this.searchForm = { keyword: "", diseaseName: "", status: "" }
this.pageForm.pageNumber = 1
this.loadPage()
},
// 进入审核详情页,具体补助公式在详情页中展示。
goReview(row) {
commonUtil.pjaxPush(this.reviewPath + "?id=" + row.id)
},
// 查看复用审核详情页,通过 mode=view 控制页面只读并隐藏审核操作。
goView(row) {
commonUtil.pjaxPush(this.reviewPath + "?id=" + row.id + "&mode=view")
},
// 统一判断审核状态,兼容后端返回数字或字符串。
isAudited(row) {
return Number(row.status) === 1
},
// 未审核状态允许进入审核或作废。
isPending(row) {
return Number(row.status) === 0
},
// 作废状态只允许取消作废,不能继续审核或弃审。
isDiscarded(row) {
return Number(row.status) === 2
},
// 已审核申请支持弃审,恢复未审核状态并清理补助明细后刷新列表。
cancelAudit(row) {
this.$confirm("确定要弃审该互助金申请吗?", "提示", {
type: "warning"
}).then(() => {
this.$axios.post(this.apiBase + "/cancelAudit", { id: row.id }).then((resp) => {
if (resp.code === 0) {
this.$message.success("弃审成功")
this.loadPage()
}
})
}).catch(() => {})
},
// 作废未审核申请,后台会记录当前操作人为审核人并记录审核时间。
discard(row) {
this.$confirm("确定要作废该互助金申请吗?", "提示", {
type: "warning"
}).then(() => {
this.$axios.post(this.apiBase + "/discard", { id: row.id }).then((resp) => {
if (resp.code === 0) {
this.$message.success("作废成功")
this.loadPage()
}
})
}).catch(() => {})
},
// 取消作废后恢复未审核状态,并清空作废时写入的审核人和审核时间。
cancelDiscard(row) {
this.$confirm("确定要取消作废该互助金申请吗?", "提示", {
type: "warning"
}).then(() => {
this.$axios.post(this.apiBase + "/cancelDiscard", { id: row.id }).then((resp) => {
if (resp.code === 0) {
this.$message.success("取消作废成功")
this.loadPage()
}
})
}).catch(() => {})
},
formatMoney(value) {
const num = Number(value || 0)
return num.toLocaleString("zh-CN", { minimumFractionDigits: 2, maximumFractionDigits: 2 })
},
// 根据内容面板剩余空间计算表格高度,避免列表高度受数据条数影响。
updateTableHeight() {
const panel = this.$refs.contentPanel
if (!panel) {
return
}
const headerHeight = this.$refs.panelHeader ? this.$refs.panelHeader.offsetHeight : 0
const pagerHeight = this.$refs.pager ? this.$refs.pager.offsetHeight : 0
this.tableHeight = Math.max(panel.clientHeight - headerHeight - pagerHeight - 32, 220)
}
},
created() {
this.loadDiseaseTypes()
this.loadPage()
},
mounted() {
this.$nextTick(this.updateTableHeight)
window.addEventListener("resize", this.updateTableHeight)
},
beforeDestroy() {
window.removeEventListener("resize", this.updateTableHeight)
}
})
</script>
<!--#}#-->
@@ -0,0 +1,275 @@
<!--#
layout("/layouts/platform.html"){
#-->
<style>
.audit-review-page { background: #f5f7fa; height: calc(100vh - 88px); display: flex; flex-direction: column; overflow: hidden; box-sizing: border-box; }
.back-bar { background: #fff; padding: 14px 24px; margin-bottom: 12px; border-bottom: 1px solid #ebeef5; flex-shrink: 0; }
.back-bar .el-button { padding: 0; font-size: 15px; }
.audit-review-card { flex: 1; min-height: 0; height: auto !important; overflow: hidden; }
.audit-review-card .card-body { flex: 1; min-height: 0; overflow-y: auto; box-sizing: border-box; }
.audit-review-card .el-card__footer { flex-shrink: 0; }
.section-title { border-left: 4px solid #0079c2; padding-left: 10px; margin: 4px 0 14px; color: #0079c2; font-weight: 700; }
.required-star { color: #f56c6c; padding-right: 4px; font-weight: 700; }
.mutual-desc .el-descriptions-item__label { width: 150px; color: #303133; background: #fafafa; }
.mutual-desc .el-descriptions-item__content { min-width: 220px; }
.mutual-desc .el-form-item { margin-bottom: 0; }
.mutual-desc .el-input-number { width: 100%; }
.calc-block { margin-top: 16px; }
.calc-table .cell { line-height: 22px; }
.limit-tip { color: #f56c6c; font-size: 13px; line-height: 22px; margin: 12px 0 0; }
.audit-actions { text-align: right; }
</style>
<div id="app" v-cloak class="audit-review-page">
<div class="back-bar">
<el-button type="text" icon="el-icon-back" @click="goBack">返回</el-button>
</div>
<custom-card class="audit-review-card" v-loading="loading">
<div class="section-title">申请信息</div>
<el-form :model="apply" label-width="0">
<el-descriptions class="mutual-desc" :column="3" border>
<el-descriptions-item>
<template slot="label"><span class="required-star">*</span>工号</template>
<el-input v-model="apply.loginName" readonly></el-input>
</el-descriptions-item>
<el-descriptions-item>
<template slot="label"><span class="required-star">*</span>姓名</template>
<el-input v-model="apply.userName" readonly></el-input>
</el-descriptions-item>
<el-descriptions-item>
<template slot="label">入会日期</template>
<el-input :value="formatDate(apply.joinDate)" readonly></el-input>
</el-descriptions-item>
<el-descriptions-item>
<template slot="label">入会时间(年)</template>
<el-input :value="calcJoinYears(apply.joinYear)" readonly></el-input>
</el-descriptions-item>
<el-descriptions-item>
<template slot="label">性别</template>
<el-input v-model="apply.sex" readonly></el-input>
</el-descriptions-item>
<el-descriptions-item>
<template slot="label">出生年月</template>
<el-input :value="formatDate(apply.birthday)" readonly></el-input>
</el-descriptions-item>
<el-descriptions-item>
<template slot="label">籍贯</template>
<el-input v-model="apply.nativePlace" readonly></el-input>
</el-descriptions-item>
<el-descriptions-item>
<template slot="label">工作部门(系部)</template>
<el-input v-model="apply.deptName" readonly></el-input>
</el-descriptions-item>
<el-descriptions-item>
<template slot="label">学历</template>
<el-input v-model="apply.education" readonly></el-input>
</el-descriptions-item>
<el-descriptions-item>
<template slot="label">职称</template>
<el-input v-model="apply.professionalTitle" readonly></el-input>
</el-descriptions-item>
<el-descriptions-item>
<template slot="label"><span class="required-star">*</span>身份证号码</template>
<el-input v-model="apply.idCard" readonly></el-input>
</el-descriptions-item>
<el-descriptions-item>
<template slot="label"><span class="required-star">*</span>联系电话</template>
<el-input v-model="apply.mobile" readonly></el-input>
</el-descriptions-item>
<el-descriptions-item>
<template slot="label"><span class="required-star">*</span>工资银行卡号</template>
<el-input v-model="apply.bankCardNo" readonly></el-input>
</el-descriptions-item>
<el-descriptions-item>
<template slot="label"><span class="required-star">*</span>开户行</template>
<el-input v-model="apply.bankName" readonly></el-input>
</el-descriptions-item>
<el-descriptions-item></el-descriptions-item>
<el-descriptions-item :span="3">
<template slot="label"><span class="required-star">*</span>大病名称</template>
<el-input v-model="apply.diseaseName" readonly></el-input>
</el-descriptions-item>
<el-descriptions-item :span="3">
<template slot="label"><span class="required-star">*</span>何时何病住何医院</template>
<el-input type="textarea" :rows="5" v-model="apply.hospitalDesc" readonly></el-input>
</el-descriptions-item>
<el-descriptions-item>
<template slot="label"><span class="required-star">*</span>全年医疗费用总金额(元)</template>
<el-input :value="formatMoney(apply.medicalTotalAmount)" readonly></el-input>
</el-descriptions-item>
<el-descriptions-item>
<template slot="label"><span class="required-star">*</span>自费金额(元)</template>
<el-input :value="formatMoney(apply.selfPayAmount)" readonly></el-input>
</el-descriptions-item>
<el-descriptions-item></el-descriptions-item>
</el-descriptions>
</el-form>
<div class="calc-block">
<div class="section-title">补助计算方法</div>
<el-table class="calc-table" :data="calcRows" border style="width: 100%">
<el-table-column type="index" label="序号" width="80" align="center"></el-table-column>
<el-table-column label="补助计算方法" align="center">
<el-table-column v-for="item in subsidies" :key="item.id || item.ratioId || item.sort" :label="item.rangeName" align="center" min-width="150">
<template slot-scope="{row}">{{ formatMoney(row.amounts[item.sort]) }}</template>
</el-table-column>
</el-table-column>
<el-table-column label="补助小计" align="center" min-width="140">
<template slot-scope="{row}">{{ formatMoney(row.total) }}</template>
</el-table-column>
<el-table-column label="补助比例" align="center" min-width="120">
<template slot-scope="{row}">{{ formatRatio(row.bonusRatio) }}</template>
</el-table-column>
<el-table-column label="实际补助金额(元)" align="center" min-width="160">
<template slot-scope="{row}">{{ formatMoney(row.actualSubsidyAmount) }}</template>
</el-table-column>
</el-table>
<el-descriptions class="mutual-desc" :column="3" border style="margin-top: 12px">
<el-descriptions-item :span="3">
<template slot="label">备注</template>
<el-input type="textarea" :rows="3" maxlength="500" show-word-limit v-model="auditForm.remark" :readonly="isViewMode" placeholder="请输入备注(选填)"></el-input>
</el-descriptions-item>
</el-descriptions>
</div>
<div class="limit-tip" v-if="showYearLimitTip">
当前申报人本年度已申报:{{ formatMoney(yearLimit.yearUsedAmount) }},剩余可申报金额:{{ formatMoney(yearLimit.yearRemainAmount) }}
</div>
<template slot="footer" v-if="!isViewMode">
<div class="audit-actions">
<el-button @click="goBack">返回</el-button>
<el-button type="primary" @click="approve">审核通过</el-button>
</div>
</template>
</custom-card>
</div>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
data() {
return {
loading: false,
id: "",
pageMode: "audit",
apiBase: "${base!}/platform/medicalMutualAid/apply/audit/review",
auditPath: "${base!}/platform/medicalMutualAid/apply/audit",
apply: {},
subsidies: [],
yearLimit: { yearUsedAmount: 0, yearRemainAmount: 0, yearLimitAmount: 0 },
auditForm: { bonusRatio: "", actualSubsidyAmount: "", remark: "" }
}
},
computed: {
// 查看模式由列表页传入,统一控制审核详情页隐藏操作按钮和锁定可编辑项。
isViewMode() {
return this.pageMode === "view"
},
// 年度同病种限额提示仅在审核计算结果返回后展示。
showYearLimitTip() {
return this.yearLimit.yearLimitAmount !== undefined && this.yearLimit.yearLimitAmount !== null
},
// 审核详情只展示一条申请的动态补助结果,表格列由后台配置区间决定。
calcRows() {
const amounts = {}
let total = 0
this.subsidies.forEach((item) => {
amounts[item.sort] = item.subsidyAmount
total += Number(item.subsidyAmount || 0)
})
return [{
amounts: amounts,
total: total,
bonusRatio: this.apply.bonusRatio,
actualSubsidyAmount: this.apply.actualSubsidyAmount
}]
}
},
methods: {
// 从 URL 中读取审核申请 ID,保持审核列表跳转参数简单明确。
getQuery(name) {
const query = window.location.search ? window.location.search.substring(1) : ""
const parts = query.split("&")
for (let i = 0; i < parts.length; i++) {
const pair = parts[i].split("=")
if (decodeURIComponent(pair[0] || "") === name) {
return decodeURIComponent(pair[1] || "")
}
}
return ""
},
// 加载审核详情,后台会同步返回基金会员入会信息和本年度启用配置计算结果。
loadDetail() {
this.loading = true
this.$axios.post(this.apiBase + "/detail", { id: this.id }).then((resp) => {
if (resp.code === 0) {
this.apply = resp.data.apply || {}
this.subsidies = resp.data.subsidies || []
this.yearLimit = {
yearUsedAmount: resp.data.yearUsedAmount || 0,
yearRemainAmount: resp.data.yearRemainAmount || 0,
yearLimitAmount: resp.data.yearLimitAmount || 0
}
this.auditForm.bonusRatio = this.formatRatio(this.apply.bonusRatio)
this.auditForm.actualSubsidyAmount = this.formatMoney(this.apply.actualSubsidyAmount)
this.auditForm.remark = this.apply.remark || ""
}
}).finally(() => {
this.loading = false
})
},
// 审核通过时提交后台重算后的补助比例、实际补助金额和审核备注。
approve() {
const params = {
id: this.id,
bonusRatio: this.apply.bonusRatio,
actualSubsidyAmount: this.apply.actualSubsidyAmount,
remark: this.auditForm.remark
}
this.$axios.post(this.apiBase + "/approve", params).then((resp) => {
if (resp.code === 0) {
this.$message.success("审核通过")
this.goBack()
}
})
},
goBack() {
commonUtil.pjaxPush(this.auditPath)
},
formatDate(value) {
if (!value) {
return ""
}
return this.$moment(value).format("YYYY-MM-DD")
},
formatRatio(value) {
if (value === undefined || value === null || value === "") {
return ""
}
return Number(value).toFixed(2)
},
// 入会时间(年)只表达基金会员入会年限,不参与表格展示列控制。
calcJoinYears(joinYear) {
if (!joinYear) {
return ""
}
const currentYear = new Date().getFullYear()
return Math.max(currentYear - Number(joinYear), 0)
},
formatMoney(value) {
const num = Number(value || 0)
const fixed = Math.floor(num * 100) / 100
const parts = fixed.toFixed(2).split(".")
return parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",") + "." + parts[1]
}
},
created() {
this.id = this.getQuery("id")
this.pageMode = this.getQuery("mode") || "audit"
this.loadDetail()
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,258 @@
<!--#
layout("/layouts/platform.html"){
#-->
<style>
.mutual-apply-page { padding: 12px; background: #f5f7fa; min-height: calc(100vh - 24px); }
.mutual-apply-card { background: #fff; border: 1px solid #e4e7ed; border-radius: 4px; padding: 16px; }
.section-title { border-left: 4px solid #0079c2; padding-left: 10px; margin: 8px 0 14px; color: #0079c2; font-weight: 700; }
.required-star { color: #f56c6c; padding-right: 4px; font-weight: 700; }
.mutual-desc .el-descriptions-item__label { width: 150px; color: #303133; background: #fafafa; }
.mutual-desc .el-descriptions-item__content { min-width: 220px; }
.mutual-desc .el-form-item { margin-bottom: 0; }
.mutual-desc .el-form-item__error { padding-top: 2px; }
.mutual-desc .el-input-number { width: 100%; }
.actions { text-align: right; padding-top: 18px; }
</style>
<div id="app" v-cloak class="mutual-apply-page">
<div class="mutual-apply-card">
<div class="section-title">申请信息</div>
<el-form :model="formData" ref="form" :rules="formRules" label-width="0" v-loading="loading">
<el-descriptions class="mutual-desc" :column="3" border>
<el-descriptions-item>
<template slot="label"><span class="required-star">*</span>工号</template>
<el-form-item prop="loginName">
<el-input v-model="formData.loginName" readonly placeholder="请输入工号"></el-input>
</el-form-item>
</el-descriptions-item>
<el-descriptions-item>
<template slot="label"><span class="required-star">*</span>姓名</template>
<el-form-item prop="userName">
<el-input v-model="formData.userName" readonly placeholder="请输入姓名"></el-input>
</el-form-item>
</el-descriptions-item>
<el-descriptions-item>
<template slot="label">性别</template>
<el-form-item prop="sex">
<el-select v-model="formData.sex" disabled placeholder="请选择性别" style="width: 100%">
<el-option label="男" value="男"></el-option>
<el-option label="女" value="女"></el-option>
</el-select>
</el-form-item>
</el-descriptions-item>
<el-descriptions-item>
<template slot="label">出生年月</template>
<el-form-item prop="birthday">
<el-date-picker v-model="formData.birthday" disabled value-format="yyyy-MM-dd" type="date" placeholder="请选择出生年月" style="width: 100%"></el-date-picker>
</el-form-item>
</el-descriptions-item>
<el-descriptions-item>
<template slot="label">籍贯</template>
<el-form-item prop="nativePlace">
<el-input v-model="formData.nativePlace" readonly placeholder="请输入籍贯"></el-input>
</el-form-item>
</el-descriptions-item>
<el-descriptions-item>
<template slot="label">工作部门(系部)</template>
<el-form-item prop="deptName">
<el-input v-model="formData.deptName" readonly placeholder="请输入工作部门(系部)"></el-input>
</el-form-item>
</el-descriptions-item>
<el-descriptions-item>
<template slot="label">学历</template>
<el-form-item prop="education">
<el-input v-model="formData.education" readonly placeholder="请输入学历"></el-input>
</el-form-item>
</el-descriptions-item>
<el-descriptions-item>
<template slot="label">职称</template>
<el-form-item prop="professionalTitle">
<el-input v-model="formData.professionalTitle" readonly placeholder="请输入职称"></el-input>
</el-form-item>
</el-descriptions-item>
<el-descriptions-item>
<template slot="label"><span class="required-star">*</span>身份证号码</template>
<el-form-item prop="idCard">
<el-input v-model="formData.idCard" placeholder="请输入身份证号码"></el-input>
</el-form-item>
</el-descriptions-item>
<el-descriptions-item>
<template slot="label"><span class="required-star">*</span>联系电话</template>
<el-form-item prop="mobile">
<el-input v-model="formData.mobile" placeholder="请输入联系电话"></el-input>
</el-form-item>
</el-descriptions-item>
<el-descriptions-item>
<template slot="label"><span class="required-star">*</span>工资银行卡号</template>
<el-form-item prop="bankCardNo">
<el-input v-model="formData.bankCardNo" placeholder="请输入工资银行卡号"></el-input>
</el-form-item>
</el-descriptions-item>
<el-descriptions-item>
<template slot="label"><span class="required-star">*</span>开户行</template>
<el-form-item prop="bankName">
<el-input v-model="formData.bankName" placeholder="请输入开户行"></el-input>
</el-form-item>
</el-descriptions-item>
<el-descriptions-item :span="3">
<template slot="label"><span class="required-star">*</span>大病名称</template>
<el-form-item prop="diseaseTypeId">
<el-select v-model="formData.diseaseTypeId" filterable placeholder="请选择大病名称" style="width: 100%" @change="handleDiseaseChange">
<el-option v-for="item in diseaseTypes" :key="item.id" :label="item.diseaseName" :value="item.id"></el-option>
</el-select>
</el-form-item>
</el-descriptions-item>
<el-descriptions-item :span="3">
<template slot="label"><span class="required-star">*</span>何时何病住何医院</template>
<el-form-item prop="hospitalDesc">
<el-input type="textarea" :rows="7" show-word-limit v-model="formData.hospitalDesc" placeholder="请详细说明何时何病住何医院"></el-input>
</el-form-item>
</el-descriptions-item>
<el-descriptions-item>
<template slot="label"><span class="required-star">*</span>全年医疗费用总金额(元)</template>
<el-form-item prop="medicalTotalAmount">
<el-input-number v-model="formData.medicalTotalAmount" :min="0" :precision="2" :step="100" controls-position="right"></el-input-number>
</el-form-item>
</el-descriptions-item>
<el-descriptions-item>
<template slot="label"><span class="required-star">*</span>自付金额(元)</template>
<el-form-item prop="selfPayAmount">
<el-input-number v-model="formData.selfPayAmount" :min="0" :precision="2" :step="100" controls-position="right"></el-input-number>
</el-form-item>
</el-descriptions-item>
<el-descriptions-item></el-descriptions-item>
<el-descriptions-item :span="3">
<template slot="label"><span class="required-star">*</span>申请人签字</template>
<el-form-item prop="signFileId">
<pc-signature v-model="formData.signFileId"></pc-signature>
</el-form-item>
</el-descriptions-item>
</el-descriptions>
</el-form>
<div class="actions">
<el-button @click="goMine">返回</el-button>
<el-button @click="saveDraft">暂存</el-button>
<el-button type="primary" @click="submitApply">提交申请</el-button>
</div>
</div>
</div>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
store,
data() {
return {
loading: false,
diseaseTypes: [],
apiBase: "${base!}/platform/medicalMutualAid/apply/form",
minePath: "${base!}/platform/medicalMutualAid/apply/mine",
formRules: {
loginName: [{required: true, message: "必填", trigger: ["change", "blur"]}],
userName: [{required: true, message: "必填", trigger: ["change", "blur"]}],
idCard: [{required: true, message: "必填", trigger: ["change", "blur"]}],
mobile: [{required: true, message: "必填", trigger: ["change", "blur"]}],
bankCardNo: [{required: true, message: "必填", trigger: ["change", "blur"]}],
bankName: [{required: true, message: "必填", trigger: ["change", "blur"]}],
diseaseTypeId: [{required: true, message: "必填", trigger: ["change", "blur"]}],
hospitalDesc: [{required: true, message: "必填", trigger: ["change", "blur"]}],
medicalTotalAmount: [{required: true, message: "必填", trigger: ["change", "blur"]}],
selfPayAmount: [{required: true, message: "必填", trigger: ["change", "blur"]}],
signFileId: [{required: true, message: "请签字", trigger: ["change", "blur"]}]
},
formData: {
status: 0,
medicalTotalAmount: 0,
selfPayAmount: 0
}
}
},
methods: {
// 初始化新增申请页面:疾病类型由后端提供,当前登录用户信息从 Vuex store 回填。
initPage() {
this.fillCurrentUser()
this.loading = true
this.$axios.post(this.apiBase + "/initApplyData").then((resp) => {
if (resp.code === 0) {
this.diseaseTypes = resp.data.diseaseTypes || []
}
}).finally(() => {
this.loading = false
})
},
// 使用平台全局 store 中的当前登录用户信息回填申请人基础字段。
fillCurrentUser() {
const user = this.$store.state.user || {}
const unit = user.unit || {}
const joinDate = user.memberJoinTime || user.joinDate || user.joinTime || ""
this.formData = Object.assign({}, this.formData, {
userId: user.id,
loginName: user.loginname,
userName: user.username,
sex: user.sex,
birthday: user.birthday,
nativePlace: user.nativePlace || user.nativeplace || "",
deptId: unit.id,
deptName: unit.name,
joinDate: joinDate,
joinYear: this.getYear(joinDate),
education: user.education || user.educationName || "",
professionalTitle: user.professionalTitle || user.title || user.positionTitle || "",
idCard: user.idCard || user.idcard || "",
mobile: user.mobile
})
},
// 从入会时间中提取年份,字段不展示但随 formData 一起提交给后台保存。
getYear(value) {
if (!value) {
return null
}
const text = String(value)
const year = Number(text.substring(0, 4))
return isNaN(year) ? null : year
},
// 选择疾病类型时同步疾病名称,避免后续档案改名影响历史申请展示。
handleDiseaseChange(value) {
const item = this.diseaseTypes.find((disease) => disease.id === value)
this.formData.diseaseName = item ? item.diseaseName : ""
},
// 暂存申请,不改变审核状态。
saveDraft() {
this.$refs.form.validate((valid) => {
if (valid) {
this.$axios.post(this.apiBase + "/saveDraft", this.formData).then((resp) => {
if (resp.code === 0) {
this.formData = resp.data
this.$message.success(resp.msg)
}
})
}
})
},
// 提交申请,状态仍为未审核,等待互助会审核。
submitApply() {
this.$refs.form.validate((valid) => {
if (valid) {
this.$axios.post(this.apiBase + "/submitApply", this.formData).then((resp) => {
if (resp.code === 0) {
this.formData = resp.data
this.$message.success(resp.msg)
window.location.href = this.minePath
}
})
}
})
},
// 返回我的申请列表,查看模式和编辑完成后共用该入口。
goMine() {
window.location.href = this.minePath
}
},
created() {
this.initPage()
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,281 @@
<!--#
layout("/layouts/platform.html"){
#-->
<style>
.mine-form-page { background: #f5f7fa; height: calc(100vh - 88px); display: flex; flex-direction: column; overflow: hidden; box-sizing: border-box; }
.back-bar { background: #fff; padding: 14px 24px; margin-bottom: 12px; border-bottom: 1px solid #ebeef5; flex-shrink: 0; }
.back-bar .el-button { padding: 0; font-size: 15px; }
.mine-form-card { flex: 1; min-height: 0; height: auto !important; overflow: hidden; }
.mine-form-card .card-body { flex: 1; min-height: 0; overflow-y: auto; box-sizing: border-box; }
.mine-form-card .el-card__footer { flex-shrink: 0; }
.required-star { color: #f56c6c; padding-right: 4px; font-weight: 700; }
.mutual-desc .el-descriptions-item__label { width: 150px; color: #303133; background: #fafafa; }
.mutual-desc .el-descriptions-item__content { min-width: 220px; }
.mutual-desc .el-form-item { margin-bottom: 0; }
.mutual-desc .el-form-item__error { padding-top: 2px; }
.mutual-desc .el-input-number { width: 100%; }
.signature-readonly { pointer-events: none; }
.actions { text-align: right; padding-top: 18px; }
</style>
<div id="app" v-cloak class="mine-form-page">
<div class="back-bar">
<el-button type="text" icon="el-icon-back" @click="goMine">返回</el-button>
</div>
<custom-card class="mine-form-card">
<el-form :model="formData" ref="form" :rules="formRules" label-width="0" v-loading="loading">
<el-descriptions class="mutual-desc" :column="3" border>
<el-descriptions-item>
<template slot="label"><span class="required-star">*</span>工号</template>
<el-form-item prop="loginName">
<el-input v-model="formData.loginName" readonly placeholder="请输入工号"></el-input>
</el-form-item>
</el-descriptions-item>
<el-descriptions-item>
<template slot="label"><span class="required-star">*</span>姓名</template>
<el-form-item prop="userName">
<el-input v-model="formData.userName" readonly placeholder="请输入姓名"></el-input>
</el-form-item>
</el-descriptions-item>
<el-descriptions-item>
<template slot="label">性别</template>
<el-form-item prop="sex">
<el-select v-model="formData.sex" disabled placeholder="请选择性别" style="width: 100%">
<el-option label="男" value="男"></el-option>
<el-option label="女" value="女"></el-option>
</el-select>
</el-form-item>
</el-descriptions-item>
<el-descriptions-item>
<template slot="label">出生年月</template>
<el-form-item prop="birthday">
<el-date-picker v-model="formData.birthday" disabled value-format="yyyy-MM-dd" type="date" placeholder="请选择出生年月" style="width: 100%"></el-date-picker>
</el-form-item>
</el-descriptions-item>
<el-descriptions-item>
<template slot="label">籍贯</template>
<el-form-item prop="nativePlace">
<el-input v-model="formData.nativePlace" readonly placeholder="请输入籍贯"></el-input>
</el-form-item>
</el-descriptions-item>
<el-descriptions-item>
<template slot="label">工作部门(系部)</template>
<el-form-item prop="deptName">
<el-input v-model="formData.deptName" readonly placeholder="请输入工作部门(系部)"></el-input>
</el-form-item>
</el-descriptions-item>
<el-descriptions-item>
<template slot="label">学历</template>
<el-form-item prop="education">
<el-input v-model="formData.education" readonly placeholder="请输入学历"></el-input>
</el-form-item>
</el-descriptions-item>
<el-descriptions-item>
<template slot="label">职称</template>
<el-form-item prop="professionalTitle">
<el-input v-model="formData.professionalTitle" readonly placeholder="请输入职称"></el-input>
</el-form-item>
</el-descriptions-item>
<el-descriptions-item>
<template slot="label"><span class="required-star">*</span>身份证号码</template>
<el-form-item prop="idCard">
<el-input v-model="formData.idCard" :readonly="readonlyMode" placeholder="请输入身份证号码"></el-input>
</el-form-item>
</el-descriptions-item>
<el-descriptions-item>
<template slot="label"><span class="required-star">*</span>联系电话</template>
<el-form-item prop="mobile">
<el-input v-model="formData.mobile" :readonly="readonlyMode" placeholder="请输入联系电话"></el-input>
</el-form-item>
</el-descriptions-item>
<el-descriptions-item>
<template slot="label"><span class="required-star">*</span>工资银行卡号</template>
<el-form-item prop="bankCardNo">
<el-input v-model="formData.bankCardNo" :readonly="readonlyMode" placeholder="请输入工资银行卡号"></el-input>
</el-form-item>
</el-descriptions-item>
<el-descriptions-item>
<template slot="label"><span class="required-star">*</span>开户行</template>
<el-form-item prop="bankName">
<el-input v-model="formData.bankName" :readonly="readonlyMode" placeholder="请输入开户行"></el-input>
</el-form-item>
</el-descriptions-item>
<el-descriptions-item :span="3">
<template slot="label"><span class="required-star">*</span>大病名称</template>
<el-form-item prop="diseaseTypeId">
<el-select v-model="formData.diseaseTypeId" filterable :disabled="readonlyMode" placeholder="请选择大病名称" style="width: 100%" @change="handleDiseaseChange">
<el-option v-for="item in diseaseTypes" :key="item.id" :label="item.diseaseName" :value="item.id"></el-option>
</el-select>
</el-form-item>
</el-descriptions-item>
<el-descriptions-item :span="3">
<template slot="label"><span class="required-star">*</span>何时何病住何医院</template>
<el-form-item prop="hospitalDesc">
<el-input type="textarea" :rows="7" show-word-limit v-model="formData.hospitalDesc" :readonly="readonlyMode" placeholder="请详细说明何时何病住何医院"></el-input>
</el-form-item>
</el-descriptions-item>
<el-descriptions-item>
<template slot="label"><span class="required-star">*</span>全年医疗费用总金额(元)</template>
<el-form-item prop="medicalTotalAmount">
<el-input-number v-model="formData.medicalTotalAmount" :disabled="readonlyMode" :min="0" :precision="2" :step="100" controls-position="right"></el-input-number>
</el-form-item>
</el-descriptions-item>
<el-descriptions-item>
<template slot="label"><span class="required-star">*</span>自付金额(元)</template>
<el-form-item prop="selfPayAmount">
<el-input-number v-model="formData.selfPayAmount" :disabled="readonlyMode" :min="0" :precision="2" :step="100" controls-position="right"></el-input-number>
</el-form-item>
</el-descriptions-item>
<el-descriptions-item></el-descriptions-item>
<el-descriptions-item :span="3">
<template slot="label"><span class="required-star">*</span>申请人签字</template>
<el-form-item prop="signFileId">
<div :class="{'signature-readonly': readonlyMode}">
<pc-signature v-model="formData.signFileId"></pc-signature>
</div>
</el-form-item>
</el-descriptions-item>
</el-descriptions>
</el-form>
<template slot="footer" v-if="!readonlyMode">
<el-button @click="saveDraft">暂存</el-button>
<el-button type="primary" @click="submitApply">提交申请</el-button>
</template>
</custom-card>
</div>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
store,
data() {
return {
loading: false,
diseaseTypes: [],
applyId: "",
pageMode: "add",
apiBase: "${base!}/platform/medicalMutualAid/apply/mine/form",
minePath: "${base!}/platform/medicalMutualAid/apply/mine",
formRules: {
loginName: [{required: true, message: "必填", trigger: ["change", "blur"]}],
userName: [{required: true, message: "必填", trigger: ["change", "blur"]}],
idCard: [{required: true, message: "必填", trigger: ["change", "blur"]}],
mobile: [{required: true, message: "必填", trigger: ["change", "blur"]}],
bankCardNo: [{required: true, message: "必填", trigger: ["change", "blur"]}],
bankName: [{required: true, message: "必填", trigger: ["change", "blur"]}],
diseaseTypeId: [{required: true, message: "必填", trigger: ["change", "blur"]}],
hospitalDesc: [{required: true, message: "必填", trigger: ["change", "blur"]}],
medicalTotalAmount: [{required: true, message: "必填", trigger: ["change", "blur"]}],
selfPayAmount: [{required: true, message: "必填", trigger: ["change", "blur"]}],
signFileId: [{required: true, message: "请签字", trigger: ["change", "blur"]}]
},
formData: {
status: 0,
medicalTotalAmount: 0,
selfPayAmount: 0
}
}
},
computed: {
// 查看模式下页面只负责展示历史申请,所有表单控件统一禁止操作。
readonlyMode() {
return this.pageMode === "view"
}
},
methods: {
// 初始化我的申请维护页面:必须携带申请ID,按模式控制查看或编辑。
initPage() {
this.applyId = this.getQueryValue("id")
this.pageMode = this.getQueryValue("mode") || "add"
if (!this.applyId) {
this.$message.error("申请ID不能为空")
this.goMine()
return
}
this.loading = true
this.$axios.post(this.apiBase + "/initApplyData").then((resp) => {
if (resp.code === 0) {
this.diseaseTypes = resp.data.diseaseTypes || []
if (this.applyId) {
return this.loadApplyDetail()
}
}
}).finally(() => {
if (!this.applyId) {
this.loading = false
}
})
},
// 读取 URL 查询参数,避免引入额外公共依赖。
getQueryValue(name) {
const query = window.location.search ? window.location.search.substring(1) : ""
const parts = query.split("&")
for (let i = 0; i < parts.length; i++) {
const item = parts[i].split("=")
if (decodeURIComponent(item[0] || "") === name) {
return decodeURIComponent(item[1] || "")
}
}
return ""
},
// 根据申请ID加载历史申请,供我的申请查看和编辑复用当前页面。
loadApplyDetail() {
return this.$axios.post(this.apiBase + "/detail", {id: this.applyId}).then((resp) => {
if (resp.code === 0) {
this.formData = resp.data || {}
}
}).finally(() => {
this.loading = false
})
},
// 选择疾病类型时同步疾病名称,避免后续档案改名影响历史申请展示。
handleDiseaseChange(value) {
const item = this.diseaseTypes.find((disease) => disease.id === value)
this.formData.diseaseName = item ? item.diseaseName : ""
},
// 暂存申请,不改变审核状态。
saveDraft() {
if (this.readonlyMode) {
return
}
this.$refs.form.validate((valid) => {
if (valid) {
this.$axios.post(this.apiBase + "/saveDraft", this.formData).then((resp) => {
if (resp.code === 0) {
this.formData = resp.data
this.$message.success(resp.msg)
}
})
}
})
},
// 提交申请,状态仍为未审核,等待互助会审核。
submitApply() {
if (this.readonlyMode) {
return
}
this.$refs.form.validate((valid) => {
if (valid) {
this.$axios.post(this.apiBase + "/submitApply", this.formData).then((resp) => {
if (resp.code === 0) {
this.formData = resp.data
this.$message.success(resp.msg)
commonUtil.pjaxPush(this.minePath)
}
})
}
})
},
// 返回我的申请列表,查看模式和编辑完成后共用该入口。
goMine() {
commonUtil.pjaxPush(this.minePath)
}
},
created() {
this.initPage()
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,213 @@
<!--#
layout("/layouts/platform.html"){
#-->
<style>
#sub-app-container-main-content { overflow: hidden; }
#sub-app-container-main-content-body { height: 100%; overflow: hidden; }
.mine-list-page { padding: 16px 20px; background: #f3f6fa; height: 100%; box-sizing: border-box; display: flex; flex-direction: column; overflow: hidden; }
.mine-search-panel { background: #fff; padding: 16px 42px; margin-bottom: 12px; box-sizing: border-box; flex-shrink: 0; }
.mine-search-panel .el-form-item { margin-bottom: 0; margin-right: 28px; }
.mine-content-panel { background: #fff; padding: 16px 20px; box-sizing: border-box; flex: 1; min-height: 0; display: flex; flex-direction: column; overflow: hidden; }
.mine-panel-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; flex-shrink: 0; }
.mine-section-title { border-left: 4px solid #0079c2; padding-left: 10px; color: #0079c2; font-size: 16px; font-weight: 700; line-height: 22px; }
.mine-table.el-table th { background: #fafafa; color: #303133; font-weight: 700; }
.mine-table.el-table td, .mine-table.el-table th { padding: 11px 0; }
.mine-actions { display: flex; align-items: center; justify-content: center; gap: 8px; }
.mine-actions .el-button { min-width: 54px; padding: 7px 14px; border-radius: 2px; margin-left: 0; }
.mine-pager { display: flex; justify-content: flex-start; align-items: center; padding-top: 12px; flex-shrink: 0; }
.mine-pager .total-text { margin-right: 18px; color: #303133; }
</style>
<div id="app" v-cloak class="mine-list-page">
<div class="mine-search-panel">
<el-form :inline="true" :model="searchForm">
<el-form-item label="大病名称">
<el-select v-model="searchForm.diseaseName" clearable filterable placeholder="请选择大病名称" style="width: 260px">
<el-option v-for="item in diseaseTypes" :key="item.id" :label="item.diseaseName" :value="item.diseaseName"></el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="search">搜索</el-button>
<el-button @click="resetSearch">重置</el-button>
</el-form-item>
</el-form>
</div>
<div class="mine-content-panel" ref="contentPanel">
<div class="mine-panel-header" ref="panelHeader">
<div class="mine-section-title">我的申请</div>
<el-button type="primary" size="small" @click="onAdd">
<i class="ti-plus"></i>
互助金申请
</el-button>
</div>
<el-table ref="mineTable" class="mine-table" :data="tableData" border v-loading="loading" :height="tableHeight" style="width: 100%">
<el-table-column type="index" label="序号" width="70" align="center"></el-table-column>
<el-table-column prop="loginName" label="工号" width="130" align="center"></el-table-column>
<el-table-column prop="userName" label="姓名" width="120" align="center"></el-table-column>
<el-table-column prop="diseaseName" label="大病名称" min-width="300" align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="全年医疗费用总金额(元)" width="190" align="center">
<template slot-scope="{row}">{{ formatMoney(row.medicalTotalAmount) }}</template>
</el-table-column>
<el-table-column label="自费金额(元)" width="160" align="center">
<template slot-scope="{row}">{{ formatMoney(row.selfPayAmount) }}</template>
</el-table-column>
<el-table-column label="状态" width="100" align="center">
<template slot-scope="{row}">
<el-tag v-if="isAudited(row)" type="success" size="mini">已审核</el-tag>
<el-tag v-else-if="isDiscarded(row)" type="danger" size="mini">已作废</el-tag>
<el-tag v-else type="warning" size="mini">未审核</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="280" align="center" fixed="right">
<template slot-scope="{row}">
<div class="mine-actions">
<el-button @click="onView(row)" size="mini" type="primary">查看</el-button>
<el-button @click="onExportApplyForm(row)" size="mini" type="primary">导出申请单</el-button>
<el-button v-if="isPending(row)" @click="onEdit(row)" size="mini" type="primary">编辑</el-button>
<el-button v-if="isPending(row)" @click="onDeleteTip" size="mini" type="danger">删除</el-button>
</div>
</template>
</el-table-column>
</el-table>
<div class="mine-pager" ref="pager">
<span class="total-text">共 {{ totalCount }} 条</span>
<el-pagination background layout="sizes, prev, pager, next" :current-page.sync="pageForm.pageNumber" :page-size.sync="pageForm.pageSize" :page-sizes="[5, 10, 20, 30, 50]" :total="totalCount" @current-change="loadPage" @size-change="loadPage"></el-pagination>
</div>
</div>
</div>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
store,
data() {
return {
loading: false,
tableData: [],
diseaseTypes: [],
totalCount: 0,
tableHeight: 360,
layoutTimer: null,
apiBase: "${base!}/platform/medicalMutualAid/apply/mine",
diseaseTypeApi: "${base!}/platform/medicalMutualAid/medical/diseaseType",
searchForm: { diseaseName: "" },
pageForm: { pageNumber: 1, pageSize: 10 }
}
},
methods: {
// 加载互助疾病类型下拉,和互助金申请页面保持同一疾病基础数据来源。
loadDiseaseTypes() {
this.$axios.post(this.diseaseTypeApi + "/pageData", { pageNumber: 1, pageSize: 999 }).then((resp) => {
if (resp.code === 0) {
this.diseaseTypes = resp.data.list || []
}
})
},
// 查询我的申请列表,使用独立分页结构保证列表高度不受通用组件影响。
loadPage() {
this.loading = true
const params = Object.assign({}, this.pageForm, this.searchForm)
this.$axios.post(this.apiBase + "/page", params).then((resp) => {
if (resp.code === 0) {
this.tableData = resp.data.list || []
this.totalCount = resp.data.totalCount || 0
}
}).finally(() => {
this.loading = false
this.scheduleTableLayout()
})
},
search() {
this.pageForm.pageNumber = 1
this.loadPage()
},
resetSearch() {
this.searchForm = { diseaseName: "" }
this.pageForm.pageNumber = 1
this.loadPage()
},
// 新增申请按平台通用页面跳转方式进入申请页。
onAdd() {
commonUtil.pjaxPush("${base!}/platform/medicalMutualAid/apply/form")
},
// 查看申请进入我的申请独立维护页,查看模式由 mode 参数控制。
onView(row) {
commonUtil.pjaxPush("${base!}/platform/medicalMutualAid/apply/mine/form?id=" + row.id + "&mode=view")
},
// 编辑申请进入我的申请独立维护页,编辑模式由 mode 参数控制。
onEdit(row) {
commonUtil.pjaxPush("${base!}/platform/medicalMutualAid/apply/mine/form?id=" + row.id + "&mode=edit")
},
// 按当前行申请ID导出申请单,后端通过系统模板管理中的 Word 模板完成参数渲染。
onExportApplyForm(row) {
window.open(this.apiBase + "/exportApplyForm?id=" + row.id)
},
onDeleteTip() {
this.$message.info("删除逻辑后续接入")
},
// 未审核申请允许个人继续编辑和删除。
isPending(row) {
return Number(row.status) === 0
},
// 已审核申请只允许查看,不能再编辑或删除。
isAudited(row) {
return Number(row.status) === 1
},
// 已作废申请只允许查看,不能再编辑或删除。
isDiscarded(row) {
return Number(row.status) === 2
},
formatMoney(value) {
if (value === null || value === undefined || value === "") {
return ""
}
return Number(value).toLocaleString("zh-CN", { minimumFractionDigits: 2, maximumFractionDigits: 2 })
},
// 根据内容面板剩余空间计算表格高度,保证列表铺满且页面不出现纵向滚动条。
updateTableHeight() {
const panel = this.$refs.contentPanel
if (!panel) {
return
}
const headerHeight = this.$refs.panelHeader ? this.$refs.panelHeader.offsetHeight : 0
const pagerHeight = this.$refs.pager ? this.$refs.pager.offsetHeight : 0
this.tableHeight = Math.max(panel.clientHeight - headerHeight - pagerHeight - 32, 260)
this.$nextTick(() => {
if (this.$refs.mineTable) {
this.$refs.mineTable.doLayout()
}
})
},
// 数据加载和窗口变化后延迟重算,避开菜单/顶部栏刷新时机导致的首屏高度误差。
scheduleTableLayout() {
if (this.layoutTimer) {
clearTimeout(this.layoutTimer)
}
this.$nextTick(() => {
this.updateTableHeight()
const vm = this
this.layoutTimer = setTimeout(function () {
vm.updateTableHeight()
}, 80)
})
}
},
created() {
this.loadDiseaseTypes()
this.loadPage()
},
mounted() {
this.scheduleTableLayout()
window.addEventListener("resize", this.scheduleTableLayout)
},
beforeDestroy() {
if (this.layoutTimer) {
clearTimeout(this.layoutTimer)
}
window.removeEventListener("resize", this.scheduleTableLayout)
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,200 @@
<!--#
layout("/layouts/platform.html"){
#-->
<style>
.mutual-list-page { padding: 16px 20px; background: #f3f6fa; height: calc(100vh - 88px); box-sizing: border-box; display: flex; flex-direction: column; overflow: hidden; }
.search-panel { background: #fff; padding: 16px 42px; margin-bottom: 12px; box-sizing: border-box; flex-shrink: 0; }
.search-panel .el-form-item { margin-bottom: 0; margin-right: 14px; }
.content-panel { background: #fff; padding: 16px 20px 16px; box-sizing: border-box; flex: 1; min-height: 0; display: flex; flex-direction: column; }
.panel-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; }
.section-title { border-left: 4px solid #0079c2; padding-left: 10px; color: #0079c2; font-size: 16px; font-weight: 700; line-height: 22px; }
.mutual-table.el-table th { background: #fafafa; color: #303133; font-weight: 700; }
.mutual-table.el-table td, .mutual-table.el-table th { padding: 11px 0; }
.pager { display: flex; justify-content: flex-start; align-items: center; padding-top: 12px; flex-shrink: 0; }
.pager .total-text { margin-right: 18px; color: #303133; }
</style>
<div id="app" v-cloak class="mutual-list-page">
<div class="search-panel">
<el-form :inline="true" :model="searchForm">
<el-form-item label="所在单位">
<el-input v-model="searchForm.deptName" placeholder="请输入所在单位" style="width: 240px"></el-input>
</el-form-item>
<el-form-item label="姓名">
<el-input v-model="searchForm.userName" placeholder="请输入姓名" style="width: 220px"></el-input>
</el-form-item>
<el-form-item label="加入年份">
<el-date-picker v-model="searchForm.joinYear" type="year" value-format="yyyy" placeholder="请选择加入年份" style="width: 220px"></el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="loadPage">查询</el-button>
<el-button @click="resetSearch">重置</el-button>
<el-button icon="el-icon-upload2" @click="exportTip">导出</el-button>
</el-form-item>
</el-form>
</div>
<div class="content-panel" ref="contentPanel">
<div class="panel-header" ref="panelHeader">
<div class="section-title">医疗互助汇总</div>
</div>
<el-table ref="summaryTable" class="mutual-table" :data="tableData" border v-loading="loading" :height="tableHeight" style="width: 100%">
<el-table-column type="index" label="序号" width="70" align="center"></el-table-column>
<el-table-column prop="deptName" label="所在单位" min-width="140" align="center"></el-table-column>
<el-table-column prop="userName" label="姓名" width="110" align="center"></el-table-column>
<el-table-column prop="joinYear" label="加入时间(年)" width="120" align="center"></el-table-column>
<el-table-column prop="diseaseText" label="大病名称及病情" min-width="190" align="center"></el-table-column>
<el-table-column label="符合规定发票金额(元)" width="160" align="center">
<template slot-scope="{row}">{{ formatMoney(row.medicalTotalAmount) }}</template>
</el-table-column>
<el-table-column label="补助计算方法" align="center">
<el-table-column v-for="item in subsidyRanges" :key="item" :label="item" min-width="130" align="center">
<template slot-scope="{row}">{{ formatMoney(row.subsidyMap[item]) }}</template>
</el-table-column>
<el-table-column label="补助小计" width="120" align="center">
<template slot-scope="{row}">{{ formatMoney(row.subsidyTotalAmount) }}</template>
</el-table-column>
</el-table-column>
<el-table-column label="补助比例" width="100" align="center">
<template slot-scope="{row}">{{ row.bonusRatio || "" }}</template>
</el-table-column>
<el-table-column label="实际补助金额(元)" width="150" align="center">
<template slot-scope="{row}">{{ formatMoney(row.actualSubsidyAmount) }}</template>
</el-table-column>
<el-table-column prop="remark" label="备注" min-width="120" align="center"></el-table-column>
</el-table>
<div class="pager" ref="pager">
<span class="total-text">共 {{ totalCount }} 条</span>
<el-pagination background layout="sizes, prev, pager, next" :current-page.sync="pageForm.pageNumber" :page-size.sync="pageForm.pageSize" :total="totalCount" @current-change="loadPage" @size-change="loadPage"></el-pagination>
</div>
</div>
</div>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
data() {
return {
loading: false,
tableData: [],
subsidyRanges: [],
totalCount: 0,
tableHeight: 360,
apiBase: "${base!}/platform/medicalMutualAid/apply/summary",
searchForm: { deptName: "", userName: "", joinYear: "" },
pageForm: { pageNumber: 1, pageSize: 10 }
}
},
methods: {
// 查询审核通过的互助金申请,并把补助明细转换成动态列数据。
loadPage() {
this.loading = true
const params = Object.assign({}, this.pageForm, this.searchForm)
this.$axios.post(this.apiBase + "/page", params).then((resp) => {
if (resp.code === 0) {
const rows = resp.data.list || []
this.tableData = rows.map((item) => this.buildRow(item))
this.totalCount = resp.data.totalCount || 0
this.refreshRanges()
}
}).finally(() => {
this.loading = false
this.scheduleTableLayout()
})
},
// 将后端主表与补助明细组合为列表展示行。
buildRow(item) {
const apply = item.apply || {}
const subsidies = item.subsidies || []
const subsidyMap = {}
subsidies.forEach((subsidy) => {
subsidyMap[subsidy.rangeName] = subsidy.subsidyAmount
})
return {
deptName: apply.deptName,
userName: apply.userName,
joinYear: apply.joinYear,
diseaseText: apply.diseaseName,
medicalTotalAmount: apply.medicalTotalAmount,
subsidyTotalAmount: apply.subsidyTotalAmount,
bonusRatio: apply.bonusRatio,
actualSubsidyAmount: apply.actualSubsidyAmount,
remark: apply.remark,
subsidies: subsidies,
subsidyMap: subsidyMap
}
},
// 根据当前页明细刷新动态补助区间表头。
refreshRanges() {
const names = []
this.tableData.forEach((row) => {
row.subsidies.forEach((item) => {
if (names.indexOf(item.rangeName) === -1) {
names.push(item.rangeName)
}
})
})
this.subsidyRanges = names
this.scheduleTableLayout()
},
resetSearch() {
this.searchForm = { deptName: "", userName: "", joinYear: "" }
this.pageForm.pageNumber = 1
this.loadPage()
},
exportTip() {
const params = []
if (this.searchForm.deptName) {
params.push("deptName=" + encodeURIComponent(this.searchForm.deptName))
}
if (this.searchForm.userName) {
params.push("userName=" + encodeURIComponent(this.searchForm.userName))
}
if (this.searchForm.joinYear) {
params.push("joinYear=" + encodeURIComponent(this.searchForm.joinYear))
}
window.open(this.apiBase + "/exportExcel" + (params.length ? "?" + params.join("&") : ""))
},
formatMoney(value) {
if (value === undefined || value === null || value === "") {
return ""
}
const num = Number(value || 0)
return num.toLocaleString("zh-CN", { minimumFractionDigits: 2, maximumFractionDigits: 2 })
},
// 根据内容面板剩余空间计算表格高度,汇总动态列只影响横向滚动,不影响页面纵向高度。
updateTableHeight() {
const panel = this.$refs.contentPanel
if (!panel) {
return
}
const headerHeight = this.$refs.panelHeader ? this.$refs.panelHeader.offsetHeight : 0
const pagerHeight = this.$refs.pager ? this.$refs.pager.offsetHeight : 0
this.tableHeight = Math.max(panel.clientHeight - headerHeight - pagerHeight - 32, 220)
this.$nextTick(() => {
if (this.$refs.summaryTable) {
this.$refs.summaryTable.doLayout()
}
})
},
// F5 刷新时动态二级表头渲染和外层布局完成顺序不稳定,延迟触发表格重排保证列宽不乱。
scheduleTableLayout() {
this.$nextTick(() => {
this.updateTableHeight()
const vm = this
setTimeout(function () {
vm.updateTableHeight()
}, 80)
})
}
},
created() {
this.loadPage()
},
mounted() {
this.scheduleTableLayout()
window.addEventListener("resize", this.scheduleTableLayout)
},
beforeDestroy() {
window.removeEventListener("resize", this.scheduleTableLayout)
}
})
</script>
<!--#}#-->
@@ -0,0 +1,689 @@
<!--#
layout("/layouts/platform.html"){
#-->
<style>
.mutual-config-page {
padding: 16px 20px;
background: #f5f7fa;
height: calc(100vh - 88px);
box-sizing: border-box;
display: flex;
flex-direction: column;
overflow: hidden;
}
.mutual-config-page .search-panel,
.mutual-config-page .list-panel {
background: #fff;
border-radius: 2px;
padding: 16px 20px;
box-sizing: border-box;
}
.mutual-config-page .search-panel {
margin-bottom: 12px;
flex-shrink: 0;
}
.mutual-config-page .list-panel {
flex: 1;
min-height: 0;
display: flex;
flex-direction: column;
}
.mutual-config-page .list-title {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
flex-shrink: 0;
}
.mutual-config-page .list-title-text {
border-left: 4px solid #0079c2;
padding-left: 10px;
color: #0079c2;
font-size: 16px;
font-weight: 700;
}
.mutual-config-page .pager-bar {
display: flex;
align-items: center;
padding-top: 12px;
flex-shrink: 0;
}
.el-dialog.mutual-edit-dialog {
margin-top: 3vh !important;
}
.mutual-edit-dialog .el-dialog__body {
height: 700px;
overflow: hidden;
padding: 16px 20px;
}
.mutual-config-page .config-dialog-body {
height: 100%;
overflow: hidden;
box-sizing: border-box;
}
.mutual-config-page .config-dialog-body .el-tabs {
display: flex;
flex-direction: column;
height: 100%;
}
.mutual-config-page .config-dialog-body .el-tabs__header {
flex: none;
margin-bottom: 0;
}
.mutual-config-page .config-dialog-body .el-tabs__content {
flex: 1;
overflow: hidden;
padding-top: 16px;
}
.mutual-config-page .config-dialog-body .el-tab-pane {
height: 100%;
overflow-y: auto;
overflow-x: hidden;
padding-right: 10px;
box-sizing: border-box;
}
.mutual-config-page .dialog-section {
border-top: 1px dashed #dcdfe6;
padding-top: 18px;
margin-top: 18px;
max-width: 100%;
overflow-x: hidden;
box-sizing: border-box;
}
.mutual-config-page .dialog-section:first-child {
border-top: 0;
padding-top: 0;
margin-top: 0;
}
.mutual-config-page .dialog-section-title {
border-left: 4px solid #0079c2;
padding-left: 10px;
margin-bottom: 16px;
color: #0079c2;
font-size: 15px;
font-weight: 700;
}
.mutual-config-page .section-toolbar {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 10px;
}
.mutual-config-page .config-dialog-body .el-form-item__label {
white-space: normal;
line-height: 20px;
padding-top: 0;
}
.mutual-config-page .config-dialog-body .aid-limit-label.is-required .el-form-item__label:before {
content: "";
margin-right: 0;
}
.mutual-config-page .config-dialog-body .aid-limit-label-text {
display: inline-block;
line-height: 20px;
text-align: right;
vertical-align: middle;
}
.mutual-config-page .config-dialog-body .aid-limit-label-star {
color: #F56C6C;
margin-right: 4px;
}
.mutual-config-page .config-dialog-body .el-table {
max-width: 100%;
}
.mutual-config-page .config-dialog-body .el-table__body-wrapper {
overflow-x: hidden;
}
.mutual-config-page .dialog-footer {
text-align: right;
}
</style>
<div id="app" v-cloak class="mutual-config-page">
<div class="search-panel">
<el-form :inline="true" :model="searchForm" label-width="70px">
<el-form-item label="年度">
<el-select v-model="searchForm.year" clearable placeholder="请选择年度" style="width: 220px">
<el-option v-for="year in yearOptions" :key="year" :label="year" :value="year"></el-option>
</el-select>
</el-form-item>
<el-form-item label="配置名称">
<el-input v-model="searchForm.configName" clearable placeholder="请输入配置名称" style="width: 260px"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" @click="loadConfigList">搜索</el-button>
<el-button @click="resetSearch">重置</el-button>
</el-form-item>
</el-form>
</div>
<div class="list-panel" ref="listPanel" v-loading="loading">
<div class="list-title" ref="listTitle">
<span class="list-title-text">医疗互助配置</span>
<el-button type="primary" icon="el-icon-plus" @click="openConfigDialog()">新增</el-button>
</div>
<el-table :data="pageList" border :height="tableHeight" style="width: 100%">
<el-table-column type="index" width="70" label="序号" align="center"></el-table-column>
<el-table-column prop="year" label="年度" width="110" align="center"></el-table-column>
<el-table-column prop="configName" label="配置名称" min-width="260" align="center"></el-table-column>
<el-table-column label="起补金额(元)" width="150" align="center">
<template slot-scope="{row}">{{ formatMoney(row.startAmount) }}</template>
</el-table-column>
<el-table-column label="第一年最高补助(元)" width="170" align="center">
<template slot-scope="{row}">{{ formatMoney(row.firstYearMaxAmount) }}</template>
</el-table-column>
<el-table-column label="其他年度最高补助(元)" width="190" align="center">
<template slot-scope="{row}">{{ formatMoney(row.otherYearMaxAmount) }}</template>
</el-table-column>
<el-table-column label="是否启用" width="110" align="center">
<template slot-scope="{row}">
<el-tag v-if="row.status === 1" type="success" size="mini">启用</el-tag>
<el-tag v-else type="info" size="mini">停用</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="170" align="center">
<template slot-scope="{row}">
<el-button type="primary" size="mini" @click="openConfigDialog(row)">编辑</el-button>
<el-button type="danger" size="mini" @click="deleteConfig(row.id)">删除</el-button>
</template>
</el-table-column>
</el-table>
<div class="pager-bar" ref="pagerBar">
<span style="margin-right: 14px">共 {{ configList.length }} 条</span>
<el-pagination
background
layout="sizes, prev, pager, next"
:current-page.sync="pageNumber"
:page-size.sync="pageSize"
:page-sizes="[10, 20, 50]"
:total="configList.length">
</el-pagination>
</div>
</div>
<el-dialog :title="configDialogTitle" :visible.sync="configDialogVisible" width="1320px" custom-class="mutual-edit-dialog" :close-on-click-modal="false">
<div class="config-dialog-body" v-loading="dialogLoading">
<el-tabs v-model="activeTab" type="card">
<el-tab-pane label="互助金补助配置" name="subsidy">
<div class="dialog-section">
<div class="dialog-section-title">基本信息</div>
<el-form :model="configForm" :rules="configRules" ref="configFormRef" label-width="160px">
<el-row :gutter="28">
<el-col :span="12">
<el-form-item label="年度" prop="year">
<el-select v-model="configForm.year" style="width: 100%" placeholder="请选择年度">
<el-option v-for="year in yearOptions" :key="year" :label="year" :value="year"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="配置名称" prop="configName">
<el-input v-model="configForm.configName" maxlength="100" placeholder="请输入配置名称"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="28">
<el-col :span="12">
<el-form-item label="起补金额(元)" prop="startAmount">
<el-input-number v-model="configForm.startAmount" :min="0" :precision="2" :step="1000" controls-position="right" style="width: 100%"></el-input-number>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="是否启用" prop="status">
<el-radio-group v-model="configForm.status">
<el-radio :label="1">启用</el-radio>
<el-radio :label="0">停用</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="28">
<el-col :span="12">
<el-form-item class="aid-limit-label" prop="firstYearMaxAmount">
<template slot="label">
<span class="aid-limit-label-text"><span class="aid-limit-label-star">*</span>第一年最高补助<br>(元)</span>
</template>
<el-input-number v-model="configForm.firstYearMaxAmount" :min="0" :precision="2" :step="1000" controls-position="right" style="width: 100%"></el-input-number>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item class="aid-limit-label" prop="otherYearMaxAmount">
<template slot="label">
<span class="aid-limit-label-text"><span class="aid-limit-label-star">*</span>其他年度最高补助<br>(元)</span>
</template>
<el-input-number v-model="configForm.otherYearMaxAmount" :min="0" :precision="2" :step="1000" controls-position="right" style="width: 100%"></el-input-number>
</el-form-item>
</el-col>
</el-row>
</el-form>
</div>
<div class="dialog-section">
<div class="section-toolbar">
<div class="dialog-section-title" style="margin-bottom: 0">补助比例配置(超额累计计算)</div>
<el-button type="primary" size="small" icon="el-icon-plus" @click="addRatioRow">新增比例</el-button>
</div>
<el-table :data="ratioList" border style="width: 100%">
<el-table-column type="index" width="70" label="序号" align="center"></el-table-column>
<el-table-column label="开始金额(元)" align="center">
<template slot-scope="{row}">
<el-input-number v-model="row.startAmount" :min="0" :precision="2" :step="1000" controls-position="right" style="width: 100%"></el-input-number>
</template>
</el-table-column>
<el-table-column label="结束金额(元)" align="center">
<template slot-scope="{row}">
<el-input-number v-model="row.endAmount" :min="0" :precision="2" :step="1000" controls-position="right" style="width: 100%"></el-input-number>
</template>
</el-table-column>
<el-table-column label="补助比例(%)" align="center">
<template slot-scope="{row}">
<el-input-number v-model="row.ratio" :min="0" :max="100" :precision="2" :step="1" controls-position="right" style="width: 100%"></el-input-number>
</template>
</el-table-column>
<el-table-column prop="remark" label="说明" min-width="220" align="center">
<template slot-scope="{row}">
<el-input v-model="row.remark" maxlength="200" placeholder="请输入说明"></el-input>
</template>
</el-table-column>
<el-table-column label="排序" width="120" align="center">
<template slot-scope="{row}">
<el-input-number v-model="row.sort" :min="1" :step="1" controls-position="right" style="width: 100%"></el-input-number>
</template>
</el-table-column>
<el-table-column label="操作" width="90" align="center">
<template slot-scope="scope">
<el-button type="danger" size="mini" @click="removeRatio(scope.row, scope.$index)">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
<div class="dialog-section">
<div class="section-toolbar">
<div class="dialog-section-title" style="margin-bottom: 0">连续未申请奖励配置</div>
<el-button type="primary" size="small" icon="el-icon-plus" @click="addBonusRow">新增奖励</el-button>
</div>
<el-table :data="bonusList" border style="width: 100%">
<el-table-column type="index" width="70" label="序号" align="center"></el-table-column>
<el-table-column prop="continuousYear" label="连续未申请年数(年)" align="center">
<template slot-scope="{row}">
<el-input-number v-model="row.continuousYear" :min="1" :step="1" controls-position="right" style="width: 100%"></el-input-number>
</template>
</el-table-column>
<el-table-column label="增加比例(%)" align="center">
<template slot-scope="{row}">
<el-input-number v-model="row.increaseRatio" :min="0" :max="100" :precision="2" :step="1" controls-position="right" style="width: 100%"></el-input-number>
</template>
</el-table-column>
<el-table-column prop="remark" label="说明" min-width="260" align="center">
<template slot-scope="{row}">
<el-input v-model="row.remark" maxlength="200" placeholder="请输入说明"></el-input>
</template>
</el-table-column>
<el-table-column label="操作" width="90" align="center">
<template slot-scope="scope">
<el-button type="danger" size="mini" @click="removeBonus(scope.row, scope.$index)">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
</el-tab-pane>
<el-tab-pane label="会员缴费配置" name="member">
<div class="dialog-section">
<div class="dialog-section-title">会员缴费配置</div>
<el-form :model="memberForm" :rules="memberRules" ref="memberFormRef" label-width="190px" style="max-width: 760px">
<el-form-item label="每月缴纳互助金额(元)" prop="monthAmount">
<el-input-number v-model="memberForm.monthAmount" :min="0" :precision="2" :step="1" controls-position="right" style="width: 320px"></el-input-number>
</el-form-item>
<el-form-item label="缴费减半年限" prop="halfPriceYear">
<el-input-number v-model="memberForm.halfPriceYear" :min="1" :step="1" controls-position="right" style="width: 320px"></el-input-number>
<span style="margin-left: 10px; color: #909399">连续缴费满该年限后,次年缴费减半</span>
</el-form-item>
</el-form>
</div>
</el-tab-pane>
</el-tabs>
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="configDialogVisible = false">取消</el-button>
<el-button type="primary" :loading="dialogSaving" @click="confirmConfigDialog">确认</el-button>
</span>
</el-dialog>
</div>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
data() {
return {
loading: false,
dialogLoading: false,
dialogSaving: false,
yearOptions: [],
searchForm: {
year: null,
configName: ""
},
configList: [],
pageNumber: 1,
pageSize: 10,
tableHeight: 360,
configDialogVisible: false,
configDialogTitle: "新增医疗互助配置",
activeTab: "subsidy",
configForm: {},
memberForm: {},
ratioList: [],
bonusList: [],
deletedRatioIds: [],
deletedBonusIds: [],
configRules: {
year: [{ required: true, message: "请选择年度", trigger: "change" }],
configName: [{ required: true, message: "请输入配置名称", trigger: "blur" }],
startAmount: [{ required: true, message: "请输入起补金额", trigger: "change" }],
firstYearMaxAmount: [{ required: true, message: "请输入第一年最高补助", trigger: "change" }],
otherYearMaxAmount: [{ required: true, message: "请输入其他年度最高补助", trigger: "change" }],
status: [{ required: true, message: "请选择状态", trigger: "change" }]
},
memberRules: {
monthAmount: [{ required: true, message: "请输入每月缴纳互助金额", trigger: "change" }],
halfPriceYear: [{ required: true, message: "请输入缴费减半年限", trigger: "change" }]
}
}
},
computed: {
pageList() {
const start = (this.pageNumber - 1) * this.pageSize
return this.configList.slice(start, start + this.pageSize)
}
},
methods: {
// 初始化年度选项,列表查询和弹窗年度录入共用同一组选项。
initYearOptions() {
const currentYear = new Date().getFullYear()
const years = []
for (let year = currentYear - 5; year <= currentYear + 2; year++) {
years.push(year)
}
this.yearOptions = years.reverse()
},
// 查询年度配置列表,每年一条配置,供页面主列表展示和检索。
loadConfigList() {
this.loading = true
this.$axios.post(loc() + "/listConfigs", this.searchForm).then((resp) => {
if (resp.code === 0) {
this.configList = resp.data || []
this.pageNumber = 1
}
}).finally(() => {
this.loading = false
this.$nextTick(this.updateTableHeight)
})
},
// 重置列表查询条件,并重新加载全部年度配置。
resetSearch() {
this.searchForm = {
year: null,
configName: ""
}
this.loadConfigList()
},
// 打开新增或编辑弹窗,编辑时加载该年度完整配置,新增时使用当前年度默认值。
openConfigDialog(row) {
this.configDialogTitle = row ? "编辑医疗互助配置" : "新增医疗互助配置"
this.activeTab = "subsidy"
this.deletedRatioIds = []
this.deletedBonusIds = []
this.configDialogVisible = true
if (!row) {
this.resetDialogForm()
return
}
this.dialogLoading = true
const year = row ? row.year : new Date().getFullYear()
this.$axios.post(loc() + "/configData", { year: year }).then((resp) => {
if (resp.code === 0) {
this.configForm = resp.data.config || this.defaultConfig(year)
this.memberForm = resp.data.memberConfig || this.defaultMemberConfig()
this.ratioList = resp.data.ratios || []
this.bonusList = resp.data.bonuses || []
}
}).finally(() => {
this.dialogLoading = false
this.$nextTick(() => {
this.$refs.configFormRef && this.$refs.configFormRef.clearValidate()
this.$refs.memberFormRef && this.$refs.memberFormRef.clearValidate()
})
})
},
// 新增弹窗初始化,仅保留年度、配置名称、启用状态和会员缴费配置默认值。
resetDialogForm() {
const year = new Date().getFullYear()
this.dialogLoading = false
this.configForm = this.defaultConfig(year)
this.memberForm = this.defaultMemberConfig()
this.ratioList = []
this.bonusList = []
this.$nextTick(() => {
this.$refs.configFormRef && this.$refs.configFormRef.clearValidate()
this.$refs.memberFormRef && this.$refs.memberFormRef.clearValidate()
})
},
// 删除年度主配置会同步删除后端已关联的比例和奖励配置。
deleteConfig(id) {
this.$confirm("确定删除此年度医疗互助配置吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
this.$axios.post(loc() + "/deleteConfig", { id: id }).then((resp) => {
if (resp.code === 0) {
this.$message.success(resp.msg)
this.loadConfigList()
}
})
}).catch(() => {})
},
// 弹窗确认时统一保存两个页签数据,避免每个页签出现独立保存按钮。
confirmConfigDialog() {
this.$refs.configFormRef.validate((configValid) => {
if (!configValid) {
this.activeTab = "subsidy"
return
}
this.$refs.memberFormRef.validate((memberValid) => {
if (!memberValid) {
this.activeTab = "member"
return
}
if (!this.validateDetailRows()) {
this.activeTab = "subsidy"
return
}
this.saveDialogData()
})
})
},
// 先保存基础配置拿到主键,再保存会员缴费、比例和奖励明细。
saveDialogData() {
this.dialogSaving = true
this.$axios.post(loc() + "/saveConfig", this.configForm).then((resp) => {
if (resp.code !== 0) {
return Promise.reject(resp)
}
const savedConfig = resp.data
const tasks = [this.$axios.post(loc() + "/saveMemberConfig", this.memberForm)]
this.deletedRatioIds.forEach((id) => {
tasks.push(this.$axios.post(loc() + "/deleteRatio", { id: id }))
})
this.deletedBonusIds.forEach((id) => {
tasks.push(this.$axios.post(loc() + "/deleteBonus", { id: id }))
})
this.ratioList.forEach((row) => {
const payload = this.cleanPayload(row)
payload.configId = savedConfig.id
tasks.push(this.$axios.post(loc() + "/saveRatio", payload))
})
this.bonusList.forEach((row) => {
const payload = this.cleanPayload(row)
payload.configId = savedConfig.id
tasks.push(this.$axios.post(loc() + "/saveBonus", payload))
})
return Promise.all(tasks).then((results) => {
for (let index = 0; index < results.length; index++) {
if (results[index].code !== 0) {
return Promise.reject(results[index])
}
}
return results
})
}).then(() => {
this.$message.success("保存成功")
this.configDialogVisible = false
this.loadConfigList()
}).catch((resp) => {
if (resp && resp.msg) {
this.$message.error(resp.msg)
}
}).finally(() => {
this.dialogSaving = false
})
},
// 新增比例时直接追加可编辑行,主弹窗确认前仅保存在前端列表中。
addRatioRow() {
this.ratioList.push({
startAmount: null,
endAmount: null,
ratio: null,
sort: null,
remark: ""
})
},
// 从弹窗内存列表移除比例,已有主键的记录延迟到主弹窗确认时删除。
removeRatio(row, index) {
if (row.id) {
this.deletedRatioIds.push(row.id)
}
this.ratioList.splice(index, 1)
},
// 新增奖励时直接追加可编辑行,主弹窗确认前仅保存在前端列表中。
addBonusRow() {
this.bonusList.push({
continuousYear: null,
increaseRatio: null,
remark: ""
})
},
// 从弹窗内存列表移除奖励,已有主键的记录延迟到主弹窗确认时删除。
removeBonus(row, index) {
if (row.id) {
this.deletedBonusIds.push(row.id)
}
this.bonusList.splice(index, 1)
},
// 统一保存前校验行内明细,避免新增空行或缺少关键字段的数据提交到后台。
validateDetailRows() {
for (let index = 0; index < this.ratioList.length; index++) {
const row = this.ratioList[index]
if (row.startAmount === null || row.startAmount === undefined || row.ratio === null || row.ratio === undefined || row.sort === null || row.sort === undefined) {
this.$message.error("请完善第 " + (index + 1) + " 行补助比例配置")
return false
}
}
for (let index = 0; index < this.bonusList.length; index++) {
const row = this.bonusList[index]
if (row.continuousYear === null || row.continuousYear === undefined || row.increaseRatio === null || row.increaseRatio === undefined) {
this.$message.error("请完善第 " + (index + 1) + " 行奖励配置")
return false
}
}
return true
},
// 新增弹窗默认基础配置,仅设置用户要求保留默认值的基础字段。
defaultConfig(year) {
return {
year: year,
configName: year + "年医疗互助补助配置",
status: 1
}
},
// 会员缴费配置为全局单条配置,未配置时给出可编辑默认值。
defaultMemberConfig() {
return {
monthAmount: 0,
halfPriceYear: 1
}
},
// 清理前端临时字段,避免弹窗编辑辅助字段传到后端实体。
cleanPayload(row) {
const payload = this.copy(row)
delete payload._editIndex
return payload
},
// 金额展示统一保留两位小数,并添加千分位分隔。
formatMoney(value) {
if (value === null || value === undefined || value === "") {
return ""
}
return Number(value).toLocaleString("zh-CN", {
minimumFractionDigits: 2,
maximumFractionDigits: 2
})
},
// 复制行数据,避免弹窗编辑时直接污染列表原数据。
copy(row) {
return JSON.parse(JSON.stringify(row))
},
// 根据列表面板剩余空间计算主表格高度,避免页面外层出现滚动条。
updateTableHeight() {
const panel = this.$refs.listPanel
if (!panel) {
return
}
const titleHeight = this.$refs.listTitle ? this.$refs.listTitle.offsetHeight : 0
const pagerHeight = this.$refs.pagerBar ? this.$refs.pagerBar.offsetHeight : 0
this.tableHeight = Math.max(panel.clientHeight - titleHeight - pagerHeight - 32, 220)
}
},
created() {
this.initYearOptions()
this.loadConfigList()
},
mounted() {
this.$nextTick(this.updateTableHeight)
window.addEventListener("resize", this.updateTableHeight)
},
beforeDestroy() {
window.removeEventListener("resize", this.updateTableHeight)
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,131 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app" v-cloak>
<guava ref="guava">
<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">
<table-tool label="疾病种类">
<el-button @click="openAdd" icon="el-icon-plus" size="small" type="primary">
新增疾病种类
</el-button>
</table-tool>
<el-table :data="tableData" :size="tableSize" @sort-change="pageOrder"
ref="table" row-key="id" style="width: 100%" v-loading="tableLoading">
<el-table-column label="编号" prop="diseaseCode"></el-table-column>
<el-table-column label="疾病种类" prop="diseaseName"></el-table-column>
<el-table-column label="是否重大疾病" prop="isMajorDiseases">
<template v-slot="{row}">
<el-tag v-if="row.isMajorDiseases" type="danger">是</el-tag>
<el-tag v-else>否</el-tag>
</template>
</el-table-column>
<el-table-column fixed="right" label="操作" width="180px">
<template slot-scope="{row}">
<el-button @click="openEdit(row)" size="mini" type="primary">编辑</el-button>
<el-button @click="doDelete(row.id)" size="mini" type="danger">删除</el-button>
</template>
</el-table-column>
</el-table>
<!--#include("/layouts/pagination.html"){}#-->
</el-card>
</guava>
<el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="50%">
<el-form :model="formData" :rules="rules" label-width="120px" ref="form">
<el-form-item label="疾病名称" prop="diseaseName">
<el-input maxlength="50" show-word-limit v-model="formData.diseaseName"></el-input>
</el-form-item>
<el-form-item label="编号" prop="diseaseCode">
<el-input maxlength="20" v-model="formData.diseaseCode"></el-input>
</el-form-item>
<el-form-item label="是否重大疾病" prop="isMajorDiseases">
<el-radio-group v-model="formData.isMajorDiseases" size="small">
<el-radio :label="true" border>是</el-radio>
<el-radio :label="false" border>否</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<el-row justify="end" type="flex">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button @click="doSubmit" class="ml10" type="primary">确定</el-button>
</el-row>
</el-dialog>
</div>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
mixins: [initTableMixins],
data() {
return {
dialogVisible: false,
dialogTitle: "新增疾病种类",
formData: {
isMajorDiseases: true
},
rules: {
diseaseName: [{ required: true, message: "请输入疾病名称", trigger: ["change", "blur"] }],
diseaseCode: [{ required: true, message: "请输入疾病编号", trigger: ["change", "blur"] }]
}
}
},
methods: {
// 删除前要求用户二次确认,避免误删疾病基础数据。
doDelete(id) {
this.$confirm("您确定要删除吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
this.$axios.post(loc() + "/doDelete", { id: id }).then((resp) => {
if (resp.code === 0) {
this.$message.success(resp.msg)
this.pageData()
}
})
}).catch(() => {})
},
// 打开编辑弹窗时复制行数据,防止未保存操作直接修改列表。
openEdit(row) {
this.formData = clone(row)
this.dialogTitle = "编辑疾病种类"
this.dialogVisible = true
},
// 表单校验通过后提交,成功时关闭弹窗并刷新列表。
doSubmit() {
this.$refs.form.validate((valid) => {
if (valid) {
this.$axios.post(loc() + "/doSubmit", this.formData).then((resp) => {
if (resp.code === 0) {
this.$message.success(resp.msg)
this.dialogVisible = false
this.pageData()
}
})
}
})
},
// 新增时重置默认值,确保重大疾病选项默认选中“是”。
openAdd() {
this.formData = { isMajorDiseases: true }
this.dialogTitle = "新增疾病种类"
this.dialogVisible = true
}
},
created() {
this.pageData()
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,210 @@
<!--#
layout("/layouts/platform_h5.html"){
#-->
<style scoped>
.disease-type-page {
min-height: 100vh;
background: #f7f8fa;
}
.search-toolbar {
display: flex;
align-items: center;
padding: 8px 12px;
background: #fff;
}
.search-toolbar .van-search {
flex: 1;
padding: 0 8px 0 0;
}
.disease-card {
margin: 10px 12px;
padding: 14px;
background: #fff;
border-radius: 8px;
}
.disease-card__header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
}
.disease-card__name {
font-size: 16px;
font-weight: 600;
color: #323233;
}
.disease-card__code {
margin-bottom: 12px;
color: #969799;
}
.disease-card__actions {
display: flex;
justify-content: flex-end;
gap: 8px;
}
.popup-title {
padding: 18px 16px 8px;
font-size: 18px;
font-weight: 600;
text-align: center;
}
.popup-actions {
padding: 16px;
}
</style>
<div class="disease-type-page" id="app">
<van-nav-bar title="疾病种类" left-text="返回" left-arrow @click-left="historyBack" fixed placeholder></van-nav-bar>
<van-sticky offset-top="46px">
<div class="search-toolbar">
<van-search v-model="pageForm.searchKeyword" placeholder="请输入疾病种类"
@clear="doSearch" @search="doSearch"></van-search>
<van-button size="small" type="info" icon="plus" @click="openAdd">新增</van-button>
</div>
</van-sticky>
<van-pull-refresh v-model="refreshing" @refresh="doSearch">
<van-list v-model="loading" :finished="finished" finished-text="没有更多了" @load="loadData">
<div class="disease-card" v-for="row in list" :key="row.id">
<div class="disease-card__header">
<span class="disease-card__name">{{row.diseaseName}}</span>
<van-tag :type="row.isMajorDiseases ? 'danger' : 'primary'">
{{row.isMajorDiseases ? "重大疾病" : "普通疾病"}}
</van-tag>
</div>
<div class="disease-card__code">编号:{{row.diseaseCode}}</div>
<div class="disease-card__actions">
<van-button plain size="small" type="info" @click="openEdit(row)">编辑</van-button>
<van-button plain size="small" type="danger" @click="doDelete(row.id)">删除</van-button>
</div>
</div>
</van-list>
</van-pull-refresh>
<van-popup v-model="dialogVisible" position="bottom" round :style="{ height: '68%' }">
<div class="popup-title">{{dialogTitle}}</div>
<van-form ref="form" @submit="doSubmit">
<van-field v-model="formData.diseaseName" name="diseaseName" label="疾病名称"
maxlength="50" placeholder="请输入疾病名称"
:rules="[{ required: true, message: '请输入疾病名称' }]"></van-field>
<van-field v-model="formData.diseaseCode" name="diseaseCode" label="编号"
maxlength="20" placeholder="请输入疾病编号"
:rules="[{ required: true, message: '请输入疾病编号' }]"></van-field>
<van-field name="isMajorDiseases" label="重大疾病">
<template #input>
<van-switch v-model="formData.isMajorDiseases" size="22"></van-switch>
</template>
</van-field>
<div class="popup-actions">
<van-button block round type="info" native-type="submit">确定</van-button>
</div>
</van-form>
</van-popup>
</div>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
store,
data() {
return {
apiPath: "/platform/h5/medicalMutualAid/medical/diseaseType",
list: [],
loading: false,
refreshing: false,
finished: false,
dialogVisible: false,
dialogTitle: "新增疾病种类",
formData: {
isMajorDiseases: true
},
pageForm: {
pageNumber: 1,
pageSize: 10,
totalCount: 0,
searchKeyword: ""
}
}
},
methods: {
// 分页加载疾病类型,刷新时替换列表,翻页时追加列表。
loadData() {
this.$axios.post(this.apiPath + "/pageData", this.pageForm).then((resp) => {
if (resp.code === 0) {
const rows = resp.data && resp.data.list ? resp.data.list : []
if (this.pageForm.pageNumber === 1) {
this.list = rows
} else {
this.list = this.list.concat(rows)
}
this.pageForm.totalCount = resp.data ? resp.data.totalCount : 0
if (this.list.length >= this.pageForm.totalCount || rows.length === 0) {
this.finished = true
} else {
this.pageForm.pageNumber += 1
}
} else {
this.$toast.fail(resp.msg)
this.finished = true
}
}).finally(() => {
this.loading = false
this.refreshing = false
})
},
// 重置分页状态并按当前关键字重新查询。
doSearch() {
this.pageForm.pageNumber = 1
this.pageForm.totalCount = 0
this.finished = false
this.list = []
this.loading = true
this.loadData()
},
// 新增时初始化默认表单。
openAdd() {
this.formData = { isMajorDiseases: true }
this.dialogTitle = "新增疾病种类"
this.dialogVisible = true
},
// 编辑时复制列表数据,避免关闭弹窗前污染原列表。
openEdit(row) {
this.formData = Object.assign({}, row)
this.dialogTitle = "编辑疾病种类"
this.dialogVisible = true
},
// Vant 表单校验成功后保存疾病类型。
doSubmit() {
this.$axios.post(this.apiPath + "/doSubmit", this.formData).then((resp) => {
if (resp.code === 0) {
this.$toast.success(resp.msg)
this.dialogVisible = false
this.doSearch()
} else {
this.$toast.fail(resp.msg)
}
})
},
// 删除前二次确认,成功后从第一页重新加载。
doDelete(id) {
this.$dialog.confirm({
title: "提示",
message: "您确定要删除吗?"
}).then(() => {
this.$axios.post(this.apiPath + "/doDelete", { id: id }).then((resp) => {
if (resp.code === 0) {
this.$toast.success(resp.msg)
this.doSearch()
} else {
this.$toast.fail(resp.msg)
}
})
}).catch(() => {})
}
}
})
</script>
<!--#
}
#-->