注册协会申请完成后导出申请表

注册协会的上传申请表字段不必填
This commit is contained in:
2026-09-21 16:30:22 +08:00
parent 71ead6854a
commit 0204dc8c92
7 changed files with 148 additions and 26 deletions
@@ -70,4 +70,12 @@ public class ClubMyApplyController {
flowEngine.processInstanceService().deleteProcessInstanceByBusinessKey(id); flowEngine.processInstanceService().deleteProcessInstanceByBusinessKey(id);
return Result.success(); return Result.success();
} }
@At
@Ok("void")
@ApiOperation("导出协会申请表")
@SaCheckPermission("club.register.myApply")
public void exportApply(@Valid String id, javax.servlet.http.HttpServletResponse response) {
sysClubService.exportApply(id, response);
}
} }
@@ -103,7 +103,6 @@ public class SysClub extends BaseModel {
@Column @Column
@Comment("申请成立报告") @Comment("申请成立报告")
@ColDefine(type = ColType.MYSQL_JSON) @ColDefine(type = ColType.MYSQL_JSON)
@NotEmpty(message = "申请成立报告不能为空")
private List<JSONObject> establishReport; private List<JSONObject> establishReport;
@Column @Column
@@ -46,9 +46,10 @@ public interface SysClubService extends BaseService<SysClub> {
void validateManagePerson(List<NutMap> managePerson); void validateManagePerson(List<NutMap> managePerson);
/** /**
* 校验协会正式提交时申请成立报告和章程草案是否已上传。 * 校验协会正式提交时章程草案是否已上传,申请成立报告为选填。
* *
* @param club 协会申请信息 * @param club 协会申请信息,rulesFile 应包含已上传的章程草案附件
* @throws com.budwk.app.base.exception.BaseException 申请信息为空或未上传章程草案;校验通过时无返回值
*/ */
void validateRequiredFiles(SysClub club); void validateRequiredFiles(SysClub club);
@@ -70,6 +71,14 @@ public interface SysClubService extends BaseService<SysClub> {
*/ */
void downloadRulesTemplate(HttpServletRequest request, HttpServletResponse response) throws IOException; void downloadRulesTemplate(HttpServletRequest request, HttpServletResponse response) throws IOException;
/**
* 按协会申请表模板导出已提交的协会申请。
*
* @param clubId 申请协会 ID
* @param response HTTP 响应,用于输出渲染后的 Word 文件
*/
void exportApply(String clubId, HttpServletResponse response);
Pagination<ClubRegisterPageVo> minePageData(@Valid ClubUserPageForm pageForm); Pagination<ClubRegisterPageVo> minePageData(@Valid ClubUserPageForm pageForm);
Pagination<ClubRegisterPageVo> schoolLeaderAuditPageData(@Valid ClubUserPageForm pageForm); Pagination<ClubRegisterPageVo> schoolLeaderAuditPageData(@Valid ClubUserPageForm pageForm);
@@ -9,7 +9,9 @@ import com.budwk.app.base.exception.BaseException;
import com.budwk.app.base.page.Pagination; import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.param.PageForm; import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.service.impl.BaseServiceImpl; import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.base.utils.CommonDownloadUtil;
import com.budwk.app.base.utils.PageUtil; import com.budwk.app.base.utils.PageUtil;
import com.budwk.app.base.utils.SysOfficeTemplateUtil;
import com.budwk.app.bpm.enums.BpmProcessInstanceStatusEnum; import com.budwk.app.bpm.enums.BpmProcessInstanceStatusEnum;
import com.budwk.app.bpm.enums.BpmProcessTaskStatusEnum; import com.budwk.app.bpm.enums.BpmProcessTaskStatusEnum;
import com.budwk.app.bpm.models.BpmProcessInstance; import com.budwk.app.bpm.models.BpmProcessInstance;
@@ -33,6 +35,9 @@ import com.budwk.app.zhgh.club.service.SysClubUserService;
import com.budwk.app.zhgh.club.vo.ClubCommonPageVo; import com.budwk.app.zhgh.club.vo.ClubCommonPageVo;
import com.budwk.app.zhgh.club.vo.ClubRegisterPageVo; import com.budwk.app.zhgh.club.vo.ClubRegisterPageVo;
import com.budwk.app.zhgh.club.vo.ClubRegisterVo; import com.budwk.app.zhgh.club.vo.ClubRegisterVo;
import com.deepoove.poi.XWPFTemplate;
import cn.hutool.json.JSONUtil;
import com.deepoove.poi.config.Configure;
import org.nutz.dao.Cnd; import org.nutz.dao.Cnd;
import org.nutz.dao.Dao; import org.nutz.dao.Dao;
import org.nutz.dao.Sqls; import org.nutz.dao.Sqls;
@@ -43,7 +48,9 @@ import org.nutz.lang.util.NutMap;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -69,6 +76,8 @@ public class SysClubServiceImpl extends BaseServiceImpl<SysClub> implements SysC
private BpmService bpmService; private BpmService bpmService;
@Inject @Inject
private SysFileService sysFileService; private SysFileService sysFileService;
@Inject
private SysOfficeTemplateUtil sysOfficeTemplateUtil;
@Override @Override
public Pagination<ClubCommonPageVo> getAllClubWithPage(PageForm pageForm, Cnd cnd) { public Pagination<ClubCommonPageVo> getAllClubWithPage(PageForm pageForm, Cnd cnd) {
@@ -126,6 +135,114 @@ public class SysClubServiceImpl extends BaseServiceImpl<SysClub> implements SysC
downloadClubTemplate("club_rules", "协会章程草案模板", request, response); downloadClubTemplate("club_rules", "协会章程草案模板", request, response);
} }
@Override
public void exportApply(String clubId, HttpServletResponse response) {
Sql clubSql = Sqls.create("""
SELECT c.clubName, c.createTime, c.introduce, c.purpose,
u.username AS applicantName, u.mobile AS applicantMobile,
u.unitName AS applicantUnitName
FROM sys_club c
LEFT JOIN vw_user u ON u.id = c.userId
WHERE c.id = @clubId
""");
clubSql.setParam("clubId", clubId);
clubSql.setCallback(Sqls.callback.map());
dao().execute(clubSql);
NutMap club = (NutMap) clubSql.getResult();
if (ObjectUtil.isEmpty(club)) {
throw new BaseException("协会申请不存在");
}
Sql memberSql = Sqls.create("""
SELECT u.username, u.unitName, u.mobile,
CASE
WHEN JSON_CONTAINS(cu.roleCode, '\"CLUB_PRESIDENT\"') THEN '会长'
WHEN JSON_CONTAINS(cu.roleCode, '\"CLUB_SECRETARY\"') THEN '秘书长'
WHEN JSON_CONTAINS(cu.roleCode, '\"CLUB_VICE_PRESIDENT\"') THEN '副会长'
WHEN JSON_CONTAINS(cu.roleCode, '\"CLUB_VICE_SECRETARY\"') THEN '副秘书长'
WHEN JSON_CONTAINS(cu.roleCode, '\"CLUB_OPERATOR\"') THEN '经办人'
ELSE '理事'
END AS roleName
FROM club_user cu
LEFT JOIN vw_user u ON u.id = cu.userId
WHERE cu.clubId = @clubId
ORDER BY CASE
WHEN JSON_CONTAINS(cu.roleCode, '\"CLUB_PRESIDENT\"') THEN 1
WHEN JSON_CONTAINS(cu.roleCode, '\"CLUB_SECRETARY\"') THEN 2
ELSE 3
END
""");
memberSql.setParam("clubId", clubId);
List<NutMap> members = listMap(memberSql);
StringBuilder managementInfo = new StringBuilder();
for (NutMap member : members) {
if (managementInfo.length() > 0) {
managementInfo.append("\n");
}
managementInfo.append(member.getString("roleName", "理事"))
.append(":").append(member.getString("username", ""))
.append(",所属单位:").append(member.getString("unitName", ""))
.append(",联系电话:").append(member.getString("mobile", ""));
}
Sql auditSql = Sqls.create("""
SELECT t.variable, t.finishTime
FROM wf_process_task t
INNER JOIN wf_process_instance ins ON ins.id = t.processInstanceId
WHERE ins.businessNo = @clubId
AND t.taskState = 20
AND t.taskName <> 'startTask'
ORDER BY t.finishTime DESC
LIMIT 1
""");
auditSql.setParam("clubId", clubId);
auditSql.setCallback(Sqls.callback.map());
dao().execute(auditSql);
NutMap audit = (NutMap) auditSql.getResult();
String schoolUnionOpinion = "";
String schoolUnionAuditTime = "";
if (ObjectUtil.isNotEmpty(audit)) {
String variable = audit.getString("variable");
if (JSONUtil.isTypeJSON(variable)) {
cn.hutool.json.JSONObject data = JSONUtil.parseObj(variable);
schoolUnionOpinion = data.getStr("tf_opinion", data.getStr("tf_approvalComment", data.getStr("approvalOpinion", "")));
}
if (audit.get("finishTime") != null) {
schoolUnionAuditTime = DateUtil.format((java.util.Date) audit.get("finishTime"), "yyyy年MM月dd日");
}
}
Map<String, Object> docData = new HashMap<>();
docData.put("clubName", club.getString("clubName", ""));
docData.put("applyTime", formatDate(club.getString("createTime")));
docData.put("applicantName", club.getString("applicantName", ""));
docData.put("applicantMobile", club.getString("applicantMobile", ""));
docData.put("applicantUnitName", club.getString("applicantUnitName", ""));
docData.put("introduce", toPlainText(club.getString("introduce", "")));
docData.put("managementInfo", managementInfo.toString());
docData.put("schoolUnionOpinion", schoolUnionOpinion);
docData.put("schoolUnionAuditTime", schoolUnionAuditTime);
try (ByteArrayOutputStream output = new ByteArrayOutputStream()) {
XWPFTemplate.compile(sysOfficeTemplateUtil.getTemplate("clubRegisterApply"), Configure.builder().build())
.render(docData).writeAndClose(output);
CommonDownloadUtil.download("教职工社团申请表(" + club.getString("clubName", "") + ").docx", output.toByteArray(), response);
} catch (IOException e) {
throw new BaseException("协会申请表导出失败", e);
}
}
private String formatDate(String value) {
if (StrUtil.isBlank(value)) {
return "";
}
return DateUtil.format(DateUtil.parse(value), "yyyy年MM月dd日");
}
private String toPlainText(String value) {
return StrUtil.blankToDefault(value, "").replaceAll("<[^>]*>", "").replace("&nbsp;", " ");
}
/** /**
* 按固定模板代码下载协会模板,模板代码仅由服务内部指定,避免下载其他业务模板。 * 按固定模板代码下载协会模板,模板代码仅由服务内部指定,避免下载其他业务模板。
*/ */
@@ -275,11 +392,8 @@ public class SysClubServiceImpl extends BaseServiceImpl<SysClub> implements SysC
@Override @Override
public void validateRequiredFiles(SysClub club) { public void validateRequiredFiles(SysClub club) {
// 文件上传组件提交空数组时,不能仅依赖 Bean Validation 的 @NotEmpty,需要在正式提交前显式校验。 // 申请成立报告为选填;章程草案仍需显式检查空附件列表,并保留申请信息为空时的保护。
if (club == null || ObjectUtil.isEmpty(club.getEstablishReport())) { if (club == null || ObjectUtil.isEmpty(club.getRulesFile())) {
throw new BaseException("请上传申请成立报告");
}
if (ObjectUtil.isEmpty(club.getRulesFile())) {
throw new BaseException("请上传章程草案"); throw new BaseException("请上传章程草案");
} }
} }
@@ -180,9 +180,7 @@ const CLUB_FORM_TEMPLATE = {
clubType: [{ required: true, message: "请选择协会类型", trigger: ["blur", "change"] }], clubType: [{ required: true, message: "请选择协会类型", trigger: ["blur", "change"] }],
concatPerson: [{ required: true, message: "请选择协会联系人", trigger: ["blur", "change"] }], concatPerson: [{ required: true, message: "请选择协会联系人", trigger: ["blur", "change"] }],
foundTime: [{ required: true, message: "请选择成立时间", trigger: ["blur", "change"] }], foundTime: [{ required: true, message: "请选择成立时间", trigger: ["blur", "change"] }],
establishReport: [{ required: true, message: "请上传申请成立报告", trigger: ["blur", "change"] }],
rulesFile: [{ required: true, message: "请上传章程草案", trigger: ["blur", "change"] }] rulesFile: [{ required: true, message: "请上传章程草案", trigger: ["blur", "change"] }]
// establishReport: [{ required: true, message: "请上传申请成立报告", trigger: ["blur", "change"] }]
//rulesFile: [{ required: true, message: "请上传章程草案", trigger: ["blur", "change"] }], //rulesFile: [{ required: true, message: "请上传章程草案", trigger: ["blur", "change"] }],
//manageFile: [{ required: true, message: "请上传经费来源及管理办法", trigger: ["blur", "change"] }], //manageFile: [{ required: true, message: "请上传经费来源及管理办法", trigger: ["blur", "change"] }],
//yearPlanFile: [{ required: true, message: "请上传年度活动计划", trigger: ["blur", "change"] }] //yearPlanFile: [{ required: true, message: "请上传年度活动计划", trigger: ["blur", "change"] }]
@@ -62,17 +62,11 @@ layout("/layouts/platform.html"){
}, },
methods: { methods: {
nextStep() { nextStep() {
let valid = true this.$refs.clubFormRef.$refs.form.validate().then(() => {
this.$refs.clubFormRef.$refs.form.validateField(
["clubName", "clubType", "foundTime", "establishReport", "rulesFile"],
(errMsg) => {
if (!errMsg) {
valid = false
}
}
)
if (valid) return
this.activeName = 1 this.activeName = 1
}).catch(() => {
this.$message.warning({ title: "警告", message: "存在必填项未填写!" })
})
}, },
async onSave() { async onSave() {
let valid = true let valid = true
@@ -100,13 +94,9 @@ layout("/layouts/platform.html"){
this.$message.warning({ title: "警告", message: "存在必填项未填写!" }) this.$message.warning({ title: "警告", message: "存在必填项未填写!" })
}) })
}, },
// 文件上传组件使用数组承载文件,提交前显式检查空数组,避免表单校验误判为已填写。 // 申请成立报告为选填;章程草案仍需上传,提交前显式检查空数组,避免表单校验误判。
validateRequiredFiles() { validateRequiredFiles() {
const formData = this.$refs.clubFormRef.formData const formData = this.$refs.clubFormRef.formData
if (!formData.establishReport || formData.establishReport.length === 0) {
this.$message.warning({ title: "警告", message: "请上传申请成立报告" })
return false
}
if (!formData.rulesFile || formData.rulesFile.length === 0) { if (!formData.rulesFile || formData.rulesFile.length === 0) {
this.$message.warning({ title: "警告", message: "请上传章程草案" }) this.$message.warning({ title: "警告", message: "请上传章程草案" })
return false return false
@@ -56,6 +56,7 @@ layout("/layouts/platform.html"){
<el-table-column label="操作" width="300"> <el-table-column label="操作" width="300">
<template v-slot="{row}"> <template v-slot="{row}">
<el-button @click="openView(row)" size="mini" type="primary">查看</el-button> <el-button @click="openView(row)" size="mini" type="primary">查看</el-button>
<el-button v-if="row.instanceState === 20" @click="exportApply(row)" size="mini" type="primary">导出申请表</el-button>
<el-button v-if="row.taskKey === 'startTask' || !row.instanceId" @click="onEdit(row)" size="mini" type="primary">编辑</el-button> <el-button v-if="row.taskKey === 'startTask' || !row.instanceId" @click="onEdit(row)" size="mini" type="primary">编辑</el-button>
<el-button v-if="row.canRevoke" size="mini" type="danger" @click="onRevoke(row)">撤回</el-button> <el-button v-if="row.canRevoke" size="mini" type="danger" @click="onRevoke(row)">撤回</el-button>
<el-button v-if="row.taskKey === 'startTask' || !row.instanceId" @click="onDelete(row.id)" size="mini" type="danger"> <el-button v-if="row.taskKey === 'startTask' || !row.instanceId" @click="onDelete(row.id)" size="mini" type="danger">
@@ -112,6 +113,9 @@ layout("/layouts/platform.html"){
onEdit(row) { onEdit(row) {
commonUtil.pjaxPush('/platform/club/register/clubRegisterApply?taskId=' + (row.startTaskId || '') + "&bizId=" + row.id) commonUtil.pjaxPush('/platform/club/register/clubRegisterApply?taskId=' + (row.startTaskId || '') + "&bizId=" + row.id)
}, },
exportApply(row) {
this.$downLoad("/platform/club/register/clubMyApply/exportApply", { id: row.id })
},
async onDelete(id) { async onDelete(id) {
const confirm = await this.$confirm("此操作将永久删除, 是否继续?", "提示", { const confirm = await this.$confirm("此操作将永久删除, 是否继续?", "提示", {
confirmButtonText: "确定", confirmButtonText: "确定",