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

注册协会的上传申请表字段不必填
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);
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
@Comment("申请成立报告")
@ColDefine(type = ColType.MYSQL_JSON)
@NotEmpty(message = "申请成立报告不能为空")
private List<JSONObject> establishReport;
@Column
@@ -46,9 +46,10 @@ public interface SysClubService extends BaseService<SysClub> {
void validateManagePerson(List<NutMap> managePerson);
/**
* 校验协会正式提交时申请成立报告和章程草案是否已上传。
* 校验协会正式提交时章程草案是否已上传,申请成立报告为选填。
*
* @param club 协会申请信息
* @param club 协会申请信息,rulesFile 应包含已上传的章程草案附件
* @throws com.budwk.app.base.exception.BaseException 申请信息为空或未上传章程草案;校验通过时无返回值
*/
void validateRequiredFiles(SysClub club);
@@ -70,6 +71,14 @@ public interface SysClubService extends BaseService<SysClub> {
*/
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> 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.param.PageForm;
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.SysOfficeTemplateUtil;
import com.budwk.app.bpm.enums.BpmProcessInstanceStatusEnum;
import com.budwk.app.bpm.enums.BpmProcessTaskStatusEnum;
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.ClubRegisterPageVo;
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.Dao;
import org.nutz.dao.Sqls;
@@ -43,7 +48,9 @@ import org.nutz.lang.util.NutMap;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@@ -69,6 +76,8 @@ public class SysClubServiceImpl extends BaseServiceImpl<SysClub> implements SysC
private BpmService bpmService;
@Inject
private SysFileService sysFileService;
@Inject
private SysOfficeTemplateUtil sysOfficeTemplateUtil;
@Override
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);
}
@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
public void validateRequiredFiles(SysClub club) {
// 文件上传组件提交空数组时,不能仅依赖 Bean Validation 的 @NotEmpty,需要在正式提交前显式校验。
if (club == null || ObjectUtil.isEmpty(club.getEstablishReport())) {
throw new BaseException("请上传申请成立报告");
}
if (ObjectUtil.isEmpty(club.getRulesFile())) {
// 申请成立报告为选填;章程草案仍需显式检查空附件列表,并保留申请信息为空时的保护。
if (club == null || ObjectUtil.isEmpty(club.getRulesFile())) {
throw new BaseException("请上传章程草案");
}
}
@@ -180,9 +180,7 @@ const CLUB_FORM_TEMPLATE = {
clubType: [{ required: true, message: "请选择协会类型", trigger: ["blur", "change"] }],
concatPerson: [{ 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"] }]
// establishReport: [{ required: true, message: "请上传申请成立报告", trigger: ["blur", "change"] }]
//rulesFile: [{ required: true, message: "请上传章程草案", trigger: ["blur", "change"] }],
//manageFile: [{ required: true, message: "请上传经费来源及管理办法", trigger: ["blur", "change"] }],
//yearPlanFile: [{ required: true, message: "请上传年度活动计划", trigger: ["blur", "change"] }]
@@ -62,17 +62,11 @@ layout("/layouts/platform.html"){
},
methods: {
nextStep() {
let valid = true
this.$refs.clubFormRef.$refs.form.validateField(
["clubName", "clubType", "foundTime", "establishReport", "rulesFile"],
(errMsg) => {
if (!errMsg) {
valid = false
}
}
)
if (valid) return
this.activeName = 1
this.$refs.clubFormRef.$refs.form.validate().then(() => {
this.activeName = 1
}).catch(() => {
this.$message.warning({ title: "警告", message: "存在必填项未填写!" })
})
},
async onSave() {
let valid = true
@@ -100,13 +94,9 @@ layout("/layouts/platform.html"){
this.$message.warning({ title: "警告", message: "存在必填项未填写!" })
})
},
// 文件上传组件使用数组承载文件,提交前显式检查空数组,避免表单校验误判为已填写。
// 申请成立报告为选填;章程草案仍需上传,提交前显式检查空数组,避免表单校验误判。
validateRequiredFiles() {
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) {
this.$message.warning({ title: "警告", message: "请上传章程草案" })
return false
@@ -56,6 +56,7 @@ layout("/layouts/platform.html"){
<el-table-column label="操作" width="300">
<template v-slot="{row}">
<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.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">
@@ -112,6 +113,9 @@ layout("/layouts/platform.html"){
onEdit(row) {
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) {
const confirm = await this.$confirm("此操作将永久删除, 是否继续?", "提示", {
confirmButtonText: "确定",