协会、活动测试整改

This commit is contained in:
2026-10-30 19:02:48 +08:00
parent 2a305a3f23
commit ceec8eb095
23 changed files with 466 additions and 172 deletions
@@ -4,12 +4,14 @@ package com.budwk.app.zhgh.activity.sports.service.impl;
import cn.afterturn.easypoi.excel.ExcelExportUtil;
import cn.afterturn.easypoi.excel.entity.ExportParams;
import cn.afterturn.easypoi.excel.entity.enmus.ExcelType;
import com.budwk.app.base.exception.BaseException;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.base.utils.CommonDownloadUtil;
import com.budwk.app.base.utils.DateUtil;
import com.budwk.app.sys.models.Sys_home_activity;
import com.budwk.app.sys.models.Sys_home_template;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.activity.basic.models.ActivityEvent;
import com.budwk.app.zhgh.activity.basic.models.ActivityBasicUnit;
import com.budwk.app.zhgh.activity.basic.models.ActivityBasicUnion;
import com.budwk.app.zhgh.activity.culture.models.ActivityTissue;
@@ -28,6 +30,7 @@ import org.nutz.dao.entity.Record;
import org.nutz.dao.sql.Sql;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.json.Json;
import org.nutz.lang.Lang;
import org.nutz.lang.Strings;
@@ -55,6 +58,7 @@ public class ActivitySportsServiceImpl extends BaseServiceImpl<ActivitySchool> i
@Override
@Aop(TransAop.READ_COMMITTED)
public String doAdd(ActivitySchool activitySchool, ActivitySchoolEvent[] events) {
events = validateAndApplySingleEventLimits(events);
ActivityBasicUnit activityBasicUnit = dao().fetch(ActivityBasicUnit.class, Cnd.where("id", "=", SecurityUtil.getUserId()));
if (activitySchool.getActivityLevel() == 40002) {
@@ -101,6 +105,7 @@ public class ActivitySportsServiceImpl extends BaseServiceImpl<ActivitySchool> i
@Override
@Aop(TransAop.READ_COMMITTED)
public void doEdit(ActivitySchool activitySchool, ActivitySchoolEvent[] events) {
events = validateAndApplySingleEventLimits(events);
ActivitySchool school = dao().fetch(ActivitySchool.class, activitySchool.getId());
if (school.getActivityLevel() == 40002) {
school.setBelongUnionId(SecurityUtil.getUnionId());
@@ -161,6 +166,56 @@ public class ActivitySportsServiceImpl extends BaseServiceImpl<ActivitySchool> i
dao().insertOrUpdate(sysHomeActivity);
}
/**
* 校验单项人数限制,并在具体单项未设置限制时应用“所有单项”的默认值。
*
* @param events 页面提交的项目配置,允许包含 eventId 为 allItems 的默认配置
* @return 已移除 allItems 临时配置、可直接持久化的项目配置数组
*/
private ActivitySchoolEvent[] validateAndApplySingleEventLimits(ActivitySchoolEvent[] events) {
if (events == null || events.length == 0) {
return new ActivitySchoolEvent[0];
}
ActivitySchoolEvent allItems = Arrays.stream(events)
.filter(event -> event != null && "allItems".equals(event.getEventId()))
.findFirst()
.orElse(null);
List<ActivitySchoolEvent> result = new ArrayList<>();
for (ActivitySchoolEvent event : events) {
if (event == null || "allItems".equals(event.getEventId())) {
continue;
}
ActivityEvent activityEvent = dao().fetch(ActivityEvent.class, event.getEventId());
if (activityEvent != null && "1".equals(activityEvent.getProjectType())) {
applySingleEventDefaultLimit(event, allItems);
}
result.add(event);
}
return result.toArray(new ActivitySchoolEvent[0]);
}
/**
* 单项的两个限制必须同时设置;均未设置或均为0时继承“所有单项”的对应值。
*/
private void applySingleEventDefaultLimit(ActivitySchoolEvent event, ActivitySchoolEvent allItems) {
Integer athletesMaxNum = event.getAthletesMaxNum();
Integer restrictRegNumber = event.getRestrictRegNumber();
if ((athletesMaxNum == null) != (restrictRegNumber == null)) {
throw new BaseException("每队(项)运动员最多人数和每人限报项目必须同时设置");
}
boolean useDefault = athletesMaxNum == null || (athletesMaxNum == 0 && restrictRegNumber == 0);
if (!useDefault) {
return;
}
if (allItems == null || allItems.getAthletesMaxNum() == null || allItems.getRestrictRegNumber() == null) {
throw new BaseException("请先设置所有单项的运动员最多人数和每人限报项目");
}
event.setAthletesMaxNum(allItems.getAthletesMaxNum());
event.setRestrictRegNumber(allItems.getRestrictRegNumber());
}
@Aop(TransAop.READ_COMMITTED)
private void addTeam(String schoolId, ActivitySchoolEvent[] events) {
if (events.length > 0) {
@@ -271,33 +326,19 @@ public class ActivitySportsServiceImpl extends BaseServiceImpl<ActivitySchool> i
WHERE
app.activityId = @id
and app.status = 2
$cnd
and (@filterUnion = 0 or app.activityUnionId = @unionId)
ORDER BY
ev.allName DESC,
u.unionname DESC,
app.`team` asc
""").setParam("id", id)
.setVar("cnd", "");
if (Strings.isNotBlank(unionId)) {
sql.setVar("cnd", "and u.unionid = @unionId");
sql.setParam("unionId", unionId);
}
""").setParam("id", id);
// 参数必须声明在原始SQL中;setVar追加的字符串不会再次解析其中的@参数。
// 未选择分工会时导出整个活动,选择时按报名记录中的活动分工会过滤。
sql.setParam("filterUnion", Strings.isNotBlank(unionId) ? 1 : 0);
sql.setParam("unionId", Strings.isNotBlank(unionId) ? unionId : "");
List<Record> list = list(sql);
String activityName;
if (Lang.isNotEmpty(list)) {
activityName = list.get(0).getString("activityName");
} else {
activityName = "暂无人员";
}
List<ActivitySchoolApply> excels = new ArrayList<>();
list.forEach(z -> {
ActivitySchoolApply schoolApply = z.toPojo(ActivitySchoolApply.class);
String s = schoolApply.getIdentity().stream().map(personType::getName).toList().toString();
schoolApply.setSf(s);
excels.add(schoolApply);
});
list.forEach(record -> excels.add(toActivitySchoolApplyExport(record)));
ExportParams exportParams = new ExportParams();
exportParams.setType(ExcelType.XSSF);
@@ -305,10 +346,42 @@ public class ActivitySportsServiceImpl extends BaseServiceImpl<ActivitySchool> i
CommonDownloadUtil.download(getUnionApplyExportFileName(unionId), workbook, response);
} catch (Exception e) {
e.printStackTrace();
throw new BaseException("导出报名信息失败:" + e.getMessage());
}
}
/**
* 将原生SQL查询结果转换为报名信息导出对象。
* identity 是数据库JSON文本,需要显式转换为身份编码集合,避免 Record.toPojo 自动转换失败导致整表无数据。
*
* @param record 导出SQL返回的一行报名数据
* @return EasyPoi 使用的 ActivitySchoolApply 导出对象
*/
private ActivitySchoolApply toActivitySchoolApplyExport(Record record) {
ActivitySchoolApply schoolApply = new ActivitySchoolApply();
schoolApply.setUnionname(record.getString("unionname"));
schoolApply.setTeam(record.getString("team"));
schoolApply.setAllName(record.getString("allName"));
schoolApply.setLoginname(record.getString("loginname"));
schoolApply.setUsername(record.getString("username"));
schoolApply.setMobile(record.getString("mobile"));
schoolApply.setIdCard(record.getString("idCard"));
schoolApply.setProfessionalLevel(record.getString("professionalLevel"));
schoolApply.setSex(record.getString("sex"));
String identityJson = record.getString("identity");
List<String> identities = Strings.isBlank(identityJson)
? new ArrayList<>()
: Json.fromJsonAsList(String.class, identityJson);
String identityName = identities.stream()
.map(personType::getName)
.filter(Strings::isNotBlank)
.collect(Collectors.joining("、"));
schoolApply.setSf(identityName);
return schoolApply;
}
private String getUnionApplyExportFileName(String unionId) {
String unionName = "分工会";
if (Strings.isNotBlank(unionId)) {
@@ -17,7 +17,6 @@ import com.budwk.app.flow.enums.ProcessInstanceStateEnum;
import com.budwk.app.flow.enums.ProcessSubmitTypeEnum;
import com.budwk.app.flow.service.FlowCommonService;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.club.model.ClubUser;
import com.budwk.app.zhgh.club.model.SysClub;
import com.budwk.app.zhgh.club.model.SysClubExamineRegister;
import com.budwk.app.zhgh.club.service.SysClubExamineService;
@@ -119,10 +118,7 @@ public class ClubExamineApplyController {
if(StrUtil.isBlank(clubId)) {
return Result.error("社团信息为空,请核查");
}
int count = dao.count(ClubUser.class, Cnd.where("clubId", "=", clubId));
SysClub club = dao.fetch(SysClub.class, clubId);
int money = count * (club.getDue() != null ? Integer.parseInt(club.getDue()) : 0);
return Result.success(money);
return Result.success(sysClubExamineService.getClubMemberMoney(clubId));
}
@At
@@ -175,6 +171,10 @@ public class ClubExamineApplyController {
public Result save(@Param("data") SysClubExamineRegister examineRegister,
@Param("incomeDetailed") String incomeDetailed,
@Param("incomeCensus") String incomeCensus) {
String validateMessage = sysClubExamineService.validateActivityRequired(examineRegister);
if (validateMessage != null) {
return Result.error(validateMessage);
}
if (StrUtil.isBlank(examineRegister.getId())) {
sysClubExamineService.doAdd(examineRegister, incomeDetailed, incomeCensus);
} else {
@@ -190,6 +190,10 @@ public class ClubExamineApplyController {
public Result submit(@Param("data") SysClubExamineRegister examineRegister,
@Param("incomeDetailed") String incomeDetailed,
@Param("incomeCensus") String incomeCensus) {
String validateMessage = sysClubExamineService.validateActivityRequired(examineRegister);
if (validateMessage != null) {
return Result.error(validateMessage);
}
SysClubExamineRegister reg;
if (StrUtil.isBlank(examineRegister.getId())) {
reg = sysClubExamineService.doAdd(examineRegister, incomeDetailed, incomeCensus);
@@ -221,6 +225,10 @@ public class ClubExamineApplyController {
@Param("incomeDetailed") String incomeDetailed,
@Param("incomeCensus") String incomeCensus,
@Param("taskId") Long taskId) {
String validateMessage = sysClubExamineService.validateActivityRequired(examineRegister);
if (validateMessage != null) {
return Result.error(validateMessage);
}
if (StrUtil.isBlank(examineRegister.getId())) {
sysClubExamineService.doAdd(examineRegister, incomeDetailed, incomeCensus);
} else {
@@ -86,7 +86,7 @@ public class ClubExamineMineController {
//nutMap.setv("schoolName", Globals.MyConfig.getString("AppName"));
nutMap.setv("clubName", register.getClubName());
nutMap.setv("create_time", register.getFoundTime());
nutMap.setv("dues_standard", register.getDue());
nutMap.setv("dues_standard", register.getDue() == null ? "--" : register.getDue() + "元/年");
// 生成材料中的年度与列表展示的登记年度保持一致。
nutMap.setv("year", register.getYear());
nutMap.setv("registerDate", register.getRegisterDate());
@@ -91,8 +91,8 @@ public class SysClub extends BaseModel {
@Column
@Comment("会费标准")
@ColDefine(type = ColType.VARCHAR, width = 50)
private String due;
@ColDefine(type = ColType.DOUBLE, width = 10, precision = 2)
private Double due;
@Column
@Comment("附件")
@@ -57,8 +57,8 @@ public class SysClubExamineRegister extends BaseModel {
@Column
@Comment("会费标准")
@ColDefine(type = ColType.VARCHAR, width = 32)
private String due;
@ColDefine(type = ColType.DOUBLE, width = 10, precision = 2)
private Double due;
@Column
@Comment("年度活动情况")
@@ -24,6 +24,21 @@ public interface SysClubExamineService extends BaseService<SysClubExamineRegiste
List<NutMap> getJgUser(@Valid String clubId);
/**
* 计算指定社团的年度会费收入。
*
* @param clubId 社团ID,用于查询当前会员数量和社团会费标准
* @return 会员数量乘以会费标准后的金额;社团或会费标准为空时返回0
*/
Double getClubMemberMoney(@Valid String clubId);
/**
* 校验考核登记的两组活动明细。
* @param examineRegister 页面data参数对应的登记实体,yearActivityList为本年度活动,plans为下一年度计划
* @return String类型;通过返回null,否则返回缺失的明细行号及字段提示
*/
String validateActivityRequired(SysClubExamineRegister examineRegister);
SysClubExamineRegister doAdd(SysClubExamineRegister examineRegister, String incomeDetailed, String incomeCensus);
SysClubExamineRegister doEdit(SysClubExamineRegister examineRegister, String incomeDetailed, String incomeCensus);
@@ -13,6 +13,7 @@ import com.budwk.app.flow.enums.ProcessInstanceStateEnum;
import com.budwk.app.flow.enums.ProcessTaskStateEnum;
import com.budwk.app.web.commons.auth.utils.AuthUtil;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.club.model.ClubUser;
import com.budwk.app.zhgh.club.model.SysClub;
import com.budwk.app.zhgh.club.model.SysClubExamineRegister;
import com.budwk.app.zhgh.club.model.SysClubExamineRegisterDetailed;
@@ -50,6 +51,15 @@ public class SysClubExamineServiceImpl extends BaseServiceImpl<SysClubExamineReg
super(dao);
}
@Override
public Double getClubMemberMoney(String clubId) {
int memberCount = dao().count(ClubUser.class, Cnd.where(ClubUser::getClubId, "=", clubId));
SysClub club = dao().fetch(SysClub.class, clubId);
// 社团或会费标准为空时按0计算,保证考核登记初始化能够正常完成。
Double due = club == null || club.getDue() == null ? 0D : club.getDue();
return memberCount * due;
}
@Override
public List<NutMap> getClubUserNum(String clubId, Integer year) {
Date yearStart = DateUtil.parse(year + "-01-01 00:00:00");
@@ -345,6 +355,40 @@ public class SysClubExamineServiceImpl extends BaseServiceImpl<SysClubExamineReg
return listMap;
}
/**
* 两组明细均至少填写一条;本年度主办单位和获奖情况保持选填。
* @param examineRegister data参数绑定的登记实体,包含yearActivityList和plans两组JSON明细
* @return 校验通过返回null,否则返回首个缺失字段的行号和名称
*/
@Override
public String validateActivityRequired(SysClubExamineRegister examineRegister) {
if (examineRegister == null) {
return "考核登记信息不能为空";
}
String message = validateActivityRows(examineRegister.getYearActivityList(), "本年度活动开展情况",
new String[]{"activityTime", "activityName", "activityAddress", "joinNum"},
new String[]{"活动时间", "活动主题/赛事名称", "活动地点", "参加人数"});
return message != null ? message : validateActivityRows(examineRegister.getPlans(), "下一年度活动计划",
new String[]{"activityName", "activityUnit", "activityAddress"},
new String[]{"活动主题/赛事名称", "活动/赛事主办单位", "活动地点"});
}
/** 检查空列表、空行及纯空白字段;数值0按已填写处理,不扩大为人数范围校验。 */
private String validateActivityRows(List<JSONObject> rows, String label, String[] fields, String[] names) {
if (rows == null || rows.isEmpty()) {
return label + "至少填写一条";
}
for (int i = 0; i < rows.size(); i++) {
JSONObject row = rows.get(i);
for (int j = 0; j < fields.length; j++) {
if (row == null || StrUtil.isBlank(row.getStr(fields[j]))) {
return label + "第" + (i + 1) + "条:" + names[j] + "不能为空";
}
}
}
return null;
}
@Override
public SysClubExamineRegister doAdd(SysClubExamineRegister examineRegister, String incomeDetailed, String incomeCensus) {
// JSON转换为实体列表(空值判断避免转换异常)
File diff suppressed because one or more lines are too long
@@ -380,7 +380,7 @@
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="setDialogVisible = false">取消</el-button>
<el-button type="primary" @click="doSetActivityUser" v-loading="settingLoading">确定</el-button>
<el-button type="primary" :disabled="settingLoading" @click="doSetActivityUser">确定</el-button>
</span>
</el-dialog>
@@ -646,7 +646,7 @@ module.exports = {
doSearch(showFullLoading = false) {
this.tableKey = new Date().getTime()
this.pageForm.pageNumber = 1
this.pageData(showFullLoading === true)
return this.pageData(showFullLoading === true)
},
async pageData(showFullLoading = false) {
this.tableLoading = true
@@ -679,43 +679,74 @@ module.exports = {
}
}
},
async doSetActivityUser() {
const valid = await this.$refs.setForm.validate()
if (!valid) {
doSetActivityUser() {
// 方法级状态保护与整页遮罩共同阻止慢请求期间的重复确认。
if (this.settingLoading) {
return
}
if (this.formData.groupType !== 2 && this.tableData.length === 0) {
this.$message.warning("请先指定筛选条件!")
return
}
const confirm = await this.$confirm("是否将符合搜索条件的用户设为活动人员?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
})
if (confirm === "confirm") {
this.settingLoading = true
const pageForm = this.normalizePageForm(this.pageForm)
Object.assign(pageForm, this.formData)
const resp = await $.post("/platform/activity/basic/scope/doSetActivityUser", {
data: JSON.stringify(pageForm)
})
if (resp.code === 0) {
this.setDialogVisible = false
await this.getActivityGroup()
this.doSearch()
this.$message.success(resp.msg)
this.formData = this.getDefaultFormData()
} else {
this.$message.error(resp.msg)
this.$refs.setForm.validate((valid) => {
if (!valid) {
return
}
this.settingLoading = false
this.$emit("update:group_id", resp.data)
this.$emit("group_change")
}
if (this.formData.groupType !== 2 && this.tableData.length === 0) {
this.$message.warning("请先指定筛选条件!")
return
}
this.$confirm("是否将符合搜索条件的用户设为活动人员?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
if (this.settingLoading) {
return
}
this.settingLoading = true
const loading = this.$loading({
lock: true,
text: "正在设置活动人员,请稍候...",
spinner: "el-icon-loading",
background: "rgba(255, 255, 255, 0.65)"
})
const pageForm = this.normalizePageForm(this.pageForm)
Object.assign(pageForm, this.formData)
$.post("/platform/activity/basic/scope/doSetActivityUser", {
data: JSON.stringify(pageForm)
}).then((resp) => {
if (resp.code !== 0) {
// 将后端业务错误转入统一失败分支,直接展示接口返回的真实原因。
throw new Error(resp.msg || "设置活动人员失败")
}
this.setDialogVisible = false
// 设置成功后等待分组和人员列表刷新完成,再解除整页操作锁定。
return this.getActivityGroup()
.then(() => this.doSearch())
.then(() => {
this.$message.success(resp.msg)
this.formData = this.getDefaultFormData()
this.$emit("update:group_id", resp.data)
this.$emit("group_change")
})
}).then(null, (error) => {
// 兼容业务错误、权限异常和网络异常,避免请求失败后页面持续加载。
const response = error && error.responseJSON ? error.responseJSON : null
const errorMessage = response && response.msg
? response.msg
: error && error.message
? error.message
: "设置活动人员失败"
this.$message.error(errorMessage)
}).always(() => {
this.settingLoading = false
loading.close()
})
})
})
},
async getActivityGroup() {
const resp = await $.get("/platform/activity/basic/scope/getActivityUserScopeGroup")
@@ -52,6 +52,7 @@ module.exports = {
*/
init() {
this.signatureAddress = origin + "/platform/signature/scanPcCode?id=" + this.id
console.log(this.signatureAddress)
return this.$axios.post("/platform/signature/get")
.then((res) => {
if (this._isBeingDestroyed || this._isDestroyed) {
@@ -75,7 +75,7 @@
<!-- 引入 form-create 和 designer -->
<script src="${base!}/assets/platform/plugins/form-create/form-create.min.js"></script>
<script src="${base!}/assets/platform/plugins/form-create/index.umd.js"></script>
<script src="${base!}/assets/platform/plugins/form-create/index.umd.js?v=20261030"></script>
<!-- swiper -->
<script src="${base!}/assets/platform/plugins/swiper/swiper-bundle.js"></script>
@@ -139,7 +139,7 @@ const act = {
}
if (act.url === "/platform/activity/apply"
&& !this.$auth.hasRoleOr(["SYSADMIN", "SCHOOL_UNION_ADMIN", "BRANCH_UNION_CHAIRMAN", "BRANCH_UNION_WENTI_SPORTS"])) {
return this.$message.warning("PC端暂未开放报名,请前往移动端报名!")
return this.$message.warning("温馨提示:您没有参加该活动的权限!")
}
window.open(act.url)
@@ -116,6 +116,69 @@ layout("/layouts/v4/baseLayout.html"){
object-fit: contain;
}
/* 最新活动的作用域样式会在组件挂载后注入,首屏先固定布局和图片尺寸,避免刷新时放大闪烁。 */
#v4-home-app .act-card .activity-section-wrapper,
#v4-home-app .act-card .section-act,
#v4-home-app .act-card .activity-swiper {
width: 100%;
min-width: 0;
max-width: 100%;
box-sizing: border-box;
overflow: hidden;
}
#v4-home-app .act-card .activity-swiper-container {
position: relative;
width: 100%;
min-width: 0;
max-width: 100%;
box-sizing: border-box;
padding: 9px 54px 12px;
overflow: hidden;
}
#v4-home-app .act-card .activity-swiper .swiper-slide {
width: 100%;
height: auto;
min-width: 0;
flex-shrink: 0;
margin-right: 18px;
}
#v4-home-app .act-card .activity-swiper .img-box {
width: 100%;
height: 128px;
border-radius: 8px;
overflow: hidden;
}
#v4-home-app .act-card .activity-swiper .img-box img {
display: block;
width: 100% !important;
height: 100% !important;
max-width: 100% !important;
max-height: 128px !important;
object-fit: fill !important;
}
@media (min-width: 768px) {
#v4-home-app .act-card .activity-swiper .swiper-slide {
width: calc((100% - 18px) / 2);
}
}
@media (min-width: 1024px) {
#v4-home-app .act-card .activity-swiper .swiper-slide {
width: calc((100% - 54px) / 4);
}
}
@media (min-width: 1200px) {
#v4-home-app .act-card .activity-swiper .swiper-slide {
width: calc((100% - 72px) / 5);
}
}
</style>
<div class="v4-container" id="v4-home-app">
@@ -221,8 +221,7 @@ layout("/layouts/platform.html"){
placeholder="请输入姓名" filterable clearable
style="width: 100%" size="small" v-if="awardsMode==1">
<el-option
v-for="item in userList"
:disabled="item.disabled"
v-for="item in getSelectableUserList(row)"
:key="item.id"
:label="item.username+'('+item.loginname+')'+item.sex"
:value="item.id">
@@ -721,10 +720,15 @@ layout("/layouts/platform.html"){
}
return score;
},
getSelectableUserList(row) {
// 其他行已经选择的人员不再显示,当前行人员保留用于姓名正常回显。
const selectedUserIds = this.userData
.map(item => item.userId)
.filter(userId => userId)
return this.userList.filter(item => item.id === row.userId || !selectedUserIds.includes(item.id))
},
userDetailsChange(val, row) {
const useridArr = this.userData.map(v => v.userId)
this.userList.forEach(v => {
v.disabled = useridArr.includes(v.id)
if (row && row.userId === v.id) {
const o = {
unionId: v.unionId,
@@ -601,6 +601,8 @@
eventForm: {
isLeader: false
},
// 独立记录当前编辑项目,避免下拉框切换后丢失切换前项目标识。
currentEventId: "",
eventsIds: [],
rightChecked: [],
checkedEventInfo: [],
@@ -778,8 +780,10 @@
this.checkedEventInfo = this.checkedEvents.map((v) => {
v.eventId = v.id
if (v.projectType === "1") {
v.athletesMaxNum = 3
v.restrictRegNumber = 2
// “所有单项”保存默认值,具体单项以0表示尚未个性化设置。
const isAllItems = v.eventId === "allItems"
v.athletesMaxNum = isAllItems ? 3 : 0
v.restrictRegNumber = isAllItems ? 2 : 0
v.leanderNum = 0
v.coachNum = 0
v.applyUserModel = 1
@@ -791,7 +795,23 @@
return v
})
},
cacheCurrentEventForm() {
if (!this.currentEventId || !this.eventForm) {
return
}
const current = clone(this.eventForm)
current.checkedEventId = this.currentEventId
current.eventId = this.currentEventId
const index = this.checkedEventInfo.findIndex((item) => item.eventId === this.currentEventId)
if (index >= 0) {
this.checkedEventInfo.splice(index, 1, current)
} else {
this.checkedEventInfo.push(current)
}
},
async eventChange(val) {
this.cacheCurrentEventForm()
const data = this.checkedEventInfo.find((v) => v.eventId === val)
const resp = await $.get("/platform/activity/sports/common/getEventData", { id: val })
await this.$nextTick()
@@ -804,10 +824,10 @@
projectType: resp.data.projectType,
leanderNum: 0,
coachNum: 0,
athletesMaxNum: resp.data.projectType === 1 ? 3 : null,
athletesMaxNum: resp.data.projectType === 1 ? 0 : null,
restrictTotalTeam: 1,
substituteNum: 0,
restrictRegNumber: resp.data.projectType === 1 ? 2 : null,
restrictRegNumber: resp.data.projectType === 1 ? 0 : null,
isLeader: false,
applyUserModel: 1
}
@@ -815,33 +835,27 @@
this.projectType = resp.data.projectType
}
this.$set(this.eventForm, "checkedEventId", val)
this.currentEventId = val
},
async saveEvent() {
saveEvent() {
const data = clone(this.eventForm)
if (data.checkedEventId === "allItems") {
this.checkedEventInfo.forEach((v) => {
if (v.projectType === "1") {
v.applyUserModel = this.eventForm.applyUserModel
v.athletesMaxNum = data.athletesMaxNum
v.restrictRegNumber = data.restrictRegNumber
v.unionLimit = data.unionLimit
}
})
const allItemsIndex = this.checkedEventInfo.findIndex((v) => v.eventId === "allItems")
if (allItemsIndex >= 0) {
this.checkedEventInfo.splice(allItemsIndex, 1, data)
}
} else {
const info = this.checkedEventInfo.find((v) => v.eventId === data.checkedEventId)
if (info != null) {
const index = this.checkedEventInfo.findIndex((v) => v.eventId === data.checkedEventId)
this.checkedEventInfo.splice(index, 1)
if (info.projectType === "1") {
this.$set(this.eventForm, "athletesMaxNum", data.athletesMaxNum)
this.$set(this.eventForm, "restrictRegNumber", data.restrictRegNumber)
}
this.$set(this.eventForm, "checkedEventId", info.eventId)
}
data.eventId = data.checkedEventId
this.checkedEventInfo.push(data)
}
await this.doSave()
this.eventForm = clone(data)
this.currentEventId = data.checkedEventId
return this.doSave()
},
prev() {
this.active--
@@ -949,6 +963,7 @@
this.copyTemplateMode = false
this.copyTemplateName = ""
this.pushTodoFlag = false
this.currentEventId = ""
this.events = await this.getEvents(2)
await this.getActivityGroup()
this.active = 0
@@ -1028,6 +1043,7 @@
async openEdit(row, flag, copyTemplateMode = false) {
this.copyTemplateMode = !!copyTemplateMode
this.pushTodoFlag = false
this.currentEventId = ""
this.active = flag ? 0 : this.active
await this.getActivityGroup()
const { id } = row
@@ -1184,7 +1200,8 @@
}
if (this.eventForm.checkedEventId == "allItems") {
events = events.filter((v) => v.projectType == 1 && v.id != "allItems")
// “所有单项”配置随具体单项提交,由后端统一校验并补齐继承值。
events = events.filter((v) => v.projectType == 1)
} else {
this.$set(this.eventForm, "activityId", this.formData.id)
this.$set(this.eventForm, "eventId", this.eventForm.checkedEventId)
@@ -1192,9 +1209,10 @@
this.$set(this.eventForm, "startAgeDate", this.eventForm.ageDate[0])
this.$set(this.eventForm, "endAgeDate", this.eventForm.ageDate[1])
}
events = this.eventForm
const allItems = this.checkedEventInfo.find((v) => v.eventId === "allItems")
events = allItems ? [clone(allItems), this.eventForm] : [this.eventForm]
if (this.copyTemplateMode) {
this.cleanCopyTemplateData(null, [events])
this.cleanCopyTemplateData(null, events)
}
}
const loading = this.$loading({
@@ -1215,6 +1233,7 @@
this.copyTemplateMode = false
}
await this.openEdit(formData, false)
this.currentEventId = this.eventForm.checkedEventId || ""
this.$message.success(resp.msg)
} else {
this.$message.error(resp.msg)
@@ -1,4 +1,11 @@
const REGISTER_INFO_COMPONENT = {
// 默认展示换届报告,仅由不需要查看该字段的业务页面显式关闭。
props: {
hideReplaceReport: {
type: Boolean,
default: false
}
},
template: `
<div>
<el-tabs tab-position="top" v-model="activeName">
@@ -36,7 +43,7 @@ const REGISTER_INFO_COMPONENT = {
{{viewData.createTime}}
</el-descriptions-item>
<el-descriptions-item label="会费标准" :span="4">
{{viewData.due}}
{{ viewData.due === null || viewData.due === undefined ? "--" : viewData.due + " 元/年" }}
</el-descriptions-item>
<el-descriptions-item :span="4">
<template slot="label">社团介绍</template>
@@ -66,7 +73,7 @@ const REGISTER_INFO_COMPONENT = {
<template slot="label">其他附件</template>
<file-preview :files="viewData.files" complete_result></file-preview>
</el-descriptions-item>
<el-descriptions-item :span="2">
<el-descriptions-item :span="2" v-if="!hideReplaceReport">
<template slot="label">换届报告</template>
<file-preview :files="viewData.replaceReport" complete_result></file-preview>
</el-descriptions-item>
@@ -68,10 +68,12 @@ layout("/layouts/platform.html"){
</el-steps>
<!-- 第一步:基础信息表单 -->
<!-- 本表单自行提示首个必填错误,关闭全局校验弹窗增强以免重复提示。 -->
<el-form
v-show="step === 0"
:model="formData"
ref="baseForm"
no-auto-enhance
:rules="baseRules"
label-width="150px"
class="main-form"
@@ -135,7 +137,9 @@ layout("/layouts/platform.html"){
v-model="formData.due"
placeholder="请输入会费标准"
readonly
></el-input>
>
<template slot="append">元/年</template>
</el-input>
</el-form-item>
</el-col>
</el-row>
@@ -146,7 +150,7 @@ layout("/layouts/platform.html"){
<el-form-item
class="is-required"
label="本年度活动开展情况"
prop="yearActivity"
prop="yearActivityList"
>
<span class="tip-text">说明:非赛事活动无需填写赛事举办单位、获奖情况。</span>
<el-table
@@ -638,6 +642,7 @@ layout("/layouts/platform.html"){
id: GetQueryString("bizId"),
taskId: GetQueryString("taskId"),
step: 0, // 当前步骤(0-基础信息,1-财务收支,2-成员名单)
formLoading: false,
isRegistered: false, // 是否已登记
currentClubId: "", // 当前选中社团ID
@@ -656,7 +661,7 @@ layout("/layouts/platform.html"){
year: new Date().getFullYear() + "", // 默认当前年度
clubId: "",
foundTime: "",
due: "",
due: null,
clubName: "",
yearActivityList: [{}], // 本年度活动列表
plans: [{}], // 下年度计划列表
@@ -671,7 +676,10 @@ layout("/layouts/platform.html"){
},
// 表单校验规则
baseRules: {},
baseRules: {
yearActivityList: [{validator: this.validateActivityRows, trigger: ['change', 'blur']}],
plans: [{validator: this.validateActivityRows, trigger: ['change', 'blur']}]
},
financeRules: {},
memberRules: {}
};
@@ -724,6 +732,28 @@ layout("/layouts/platform.html"){
}
},
methods: {
// 在列表级逐行校验,避免嵌套表单项未注册、空数组或纯空白文本绕过必填。
validateActivityRows(rule, value, callback) {
const currentYear = rule.field === 'yearActivityList';
const label = currentYear ? '本年度活动开展情况' : '下一年度活动计划';
const fields = currentYear
? [['activityTime', '活动时间'], ['activityName', '活动主题/赛事名称'], ['activityAddress', '活动地点'], ['joinNum', '参加人数']]
: [['activityName', '活动主题/赛事名称'], ['activityUnit', '活动/赛事主办单位'], ['activityAddress', '活动地点']];
if (!Array.isArray(value) || value.length === 0) {
callback(new Error(label + '至少填写一条'));
return;
}
for (let i = 0; i < value.length; i++) {
for (const field of fields) {
const content = value[i] && value[i][field[0]];
if (content === null || content === undefined || String(content).trim() === '') {
callback(new Error(label + '第' + (i + 1) + '条:' + field[1] + '不能为空'));
return;
}
}
}
callback();
},
/************************ 通用工具方法 ************************/
// 格式化数字(保留2位小数,空值显示0)
formatNumber(num) {
@@ -1110,7 +1140,7 @@ layout("/layouts/platform.html"){
if (!val) return;
const club = this.clubOptions.find(item => item.id === val);
// 更新社团基础信息
this.formData.due = club?.due || "";
this.formData.due = club && club.due !== null && club.due !== undefined ? club.due : null;
this.formData.foundTime = club?.foundTime
? this.$moment(club.foundTime).format("YYYY-MM-DD")
: this.$moment().format("YYYY-MM-DD");
@@ -1161,73 +1191,63 @@ layout("/layouts/platform.html"){
// 步骤前进
this.step++;
},
// 保存
async handleSave() {
try {
this.recalculateCensus();
const resp = await this.$axios.post("/platform/club/examine/apply/save", {
data: JSON.stringify(this.formData),
incomeDetailed: JSON.stringify(this.financeData.incomeDetailed),
incomeCensus: JSON.stringify(this.financeData.incomeCensus)
});
if (resp.code === 0) {
this.$message.success(resp.msg);
commonUtil.pjaxPush('/platform/club/examine/mine');
// 三个操作共用基础信息校验,失败时返回第一步展示行号和必填字段。
validateBaseBeforeAction(action) {
if (this.formLoading) return;
this.$refs.baseForm.validate((valid, fields) => {
if (!valid) {
this.step = 0;
const firstError = Object.values(fields || {}).flat()[0];
this.$message.warning('温馨提示:' + (firstError ? firstError.message : '请补全基础信息必填项'));
return;
}
} catch (error) {
this.handleError(error, "保存失败");
}
action();
});
},
// 提交(新增)
async handleSubmit() {
try {
this.recalculateCensus();
await this.$confirm("确定要提交此申请吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "info"
// 仅接收本页三个固定操作的接口后缀;data为登记实体JSON,另两项为财务数组JSON。
// 接口返回{code,msg,data},code为0时保存成功,否则展示后端校验提示。
requestExamineAction(action) {
if (this.formLoading) return;
this.recalculateCensus();
const params = {
data: JSON.stringify(this.formData),
incomeDetailed: JSON.stringify(this.financeData.incomeDetailed),
incomeCensus: JSON.stringify(this.financeData.incomeCensus)
};
if (action === 'submitAgain') params.taskId = this.taskId;
this.formLoading = true;
return this.$axios.post('/platform/club/examine/apply/' + action, params)
.then((resp) => {
if (resp.code === 0) {
this.$message.success(resp.msg);
commonUtil.pjaxPush('/platform/club/examine/mine');
} else {
this.$message.warning(resp.msg);
}
})
.catch(() => {
// 后端校验和网络异常已由全局axios拦截器提示,此处仅结束失败请求,避免再次弹窗。
})
.finally(() => {
this.formLoading = false;
});
const resp = await this.$axios.post("/platform/club/examine/apply/submit", {
data: JSON.stringify(this.formData),
incomeDetailed: JSON.stringify(this.financeData.incomeDetailed),
incomeCensus: JSON.stringify(this.financeData.incomeCensus)
});
if (resp.code === 0) {
this.$message.success(resp.msg);
commonUtil.pjaxPush('/platform/club/examine/mine');
} else {
this.$message.warning(resp.msg);
}
} catch (error) {
if (error !== "cancel") {
this.handleError(error, "提交失败");
}
}
},
// 提交(任务处理)
async handleFinishTask() {
try {
this.recalculateCensus();
await this.$confirm("您确定要提交吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
});
const resp = await this.$axios.post('/platform/club/examine/apply/submitAgain', {
data: JSON.stringify(this.formData),
incomeDetailed: JSON.stringify(this.financeData.incomeDetailed),
incomeCensus: JSON.stringify(this.financeData.incomeCensus),
taskId: this.taskId
});
if (resp.code === 0) {
this.$message.success(resp.msg);
commonUtil.pjaxPush('/platform/club/examine/mine');
}
} catch (error) {
if (error !== "cancel") {
this.handleError(error, "提交失败");
}
}
handleSave() {
this.validateBaseBeforeAction(() => this.requestExamineAction('save'));
},
handleSubmit() {
this.validateBaseBeforeAction(() => {
this.$confirm('确定要提交此申请吗?', '提示', {
confirmButtonText: '确定', cancelButtonText: '取消', type: 'info'
}).then(() => this.requestExamineAction('submit')).catch(() => {});
});
},
handleFinishTask() {
this.validateBaseBeforeAction(() => {
this.$confirm('您确定要提交吗?', '提示', {
confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning'
}).then(() => this.requestExamineAction('submitAgain')).catch(() => {});
});
}
}
});
@@ -10,7 +10,7 @@
<el-descriptions v-if="viewData" :column="3" border class="table_fixed">
<el-descriptions-item label="社团名称">{{ viewData.clubName }}</el-descriptions-item>
<el-descriptions-item label="成立时间">{{ viewData.foundTime }} </el-descriptions-item>
<el-descriptions-item label="会费标准">{{ viewData.due }}</el-descriptions-item>
<el-descriptions-item label="会费标准">{{ viewData.due === null || viewData.due === undefined ? "--" : viewData.due + " 元/年" }}</el-descriptions-item>
<el-descriptions-item span="3">
<template slot="label">工作总结</template>
<file-preview :files="viewData.summaryFiles" complete_result></file-preview>
@@ -73,7 +73,7 @@ layout("/layouts/platform.html"){
<el-descriptions-item label="联系人邮箱">{{viewData.concatPersonEmail}}</el-descriptions-item>
<el-descriptions-item>
<template slot="label">会费标准</template>
{{viewData.due}}
{{ viewData.due === null || viewData.due === undefined ? "--" : viewData.due + " 元/年" }}
</el-descriptions-item>
<el-descriptions-item :span="2">
<template slot="label">当前人数</template>
@@ -63,7 +63,7 @@ layout("/layouts/platform.html"){
<el-descriptions-item label="社团编码">{{viewData.clubCode}}</el-descriptions-item>
<el-descriptions-item label="社团类型">{{viewData.typeName}}</el-descriptions-item>
<el-descriptions-item label="发起人">{{viewData.sponsorName}}</el-descriptions-item>
<el-descriptions-item label="会费标准">{{viewData.due}}</el-descriptions-item>
<el-descriptions-item label="会费标准">{{ viewData.due === null || viewData.due === undefined ? "--" : viewData.due + " 元/年" }}</el-descriptions-item>
<el-descriptions-item label="当前人数">{{viewData.currentPeopleNum}}</el-descriptions-item>
<el-descriptions-item span="2">
<template slot="label">社团介绍</template>
@@ -24,7 +24,13 @@ const CLUB_FORM_TEMPLATE = {
</el-col>
<el-col :span="12">
<el-form-item label="会费标准" prop="due">
<el-input placeholder="请填写会费标准" v-model="formData.due">
<el-input
min="0"
placeholder="请填写会费标准"
step="0.01"
type="number"
v-model.number="formData.due"
>
<template slot="append">元/年</template>
</el-input>
</el-form-item>
@@ -156,12 +162,15 @@ const CLUB_FORM_TEMPLATE = {
return {
formData: {
clubName: "",
clubType: ""
clubType: "",
due: null
},
formRules: {
clubName: [{ required: true, message: "请填写社团名称", trigger: ["blur", "change"] }],
clubCode: [{ required: false, message: "请填写社团编码", trigger: ["blur", "change"] }],
clubType: [{ required: true, message: "请选择社团类型", trigger: ["blur", "change"] }],
// 会费标准只保存数字,单位“元/年”由页面统一展示。
due: [{ type: "number", min: 0, message: "会费标准必须为不小于0的数字", trigger: ["blur", "change"] }],
//concatPerson: [{ required: true, message: "请选择社团联系人", trigger: ["blur", "change"] }],
foundTime: [{ required: true, message: "请选择成立时间", trigger: ["blur", "change"] }]
}
@@ -73,7 +73,7 @@ layout("/layouts/platform.html"){
</el-card>
<template #public>
<register-info ref="registerInfoRef">
<register-info ref="registerInfoRef" :hide-replace-report="true">
<div v-if="showApprovalForm">
<div class="process-title">
{{formData.taskName}}
@@ -77,7 +77,7 @@ layout("/layouts/platform_h5.html"){
<van-cell title="会长">{{ clubDetail.clubLeader || '--' }}</van-cell>
<van-cell title="秘书长">{{ clubDetail.clubSecretary || '--' }}</van-cell>
<van-cell title="当前人数">{{ clubDetail.currentPeopleNum || 0 }} 人</van-cell>
<van-cell title="会费标准">{{ clubDetail.due || '--' }}</van-cell>
<van-cell title="会费标准">{{ clubDetail.due === null || clubDetail.due === undefined ? '--' : clubDetail.due + ' 元/年' }}</van-cell>
<van-cell title="社团介绍" class="club-user-join-info-long-cell"><div v-if="hasClubContent(clubDetail.introduce)" v-html="clubDetail.introduce"></div><span v-else>暂无</span></van-cell>
<van-cell title="社团宗旨" class="club-user-join-info-long-cell"><div v-if="hasClubContent(clubDetail.purpose)" v-html="clubDetail.purpose"></div><span v-else>暂无</span></van-cell>
<van-cell title="社团章程" class="club-user-join-info-long-cell"><file-preview v-if="clubDetail.rulesFile && clubDetail.rulesFile.length" :files="clubDetail.rulesFile" complete_result></file-preview><span v-else>暂无</span></van-cell>