Author SHA1 Message Date
c-zhouhf1 637fe6f838 整改民主管理测试---芳姐 2026-10-31 18:04:30 +08:00
c-zhouhf1 4f85bb08e2 经费提案整改 2026-10-30 15:50:53 +08:00
41 changed files with 629 additions and 801 deletions
@@ -19,6 +19,7 @@ RoleConstant {
SCHOOL_UNION_ADMIN("校工会管理员"), SCHOOL_UNION_ADMIN("校工会管理员"),
SCHOOL_UNION_CHAIRMAN("校工会主席"), SCHOOL_UNION_CHAIRMAN("校工会主席"),
SCHOOL_UNION_VICE_CHAIRMAN("校工会副主席"), SCHOOL_UNION_VICE_CHAIRMAN("校工会副主席"),
SCHOOL_UNION_FINANCE("校工会财务"),
SCHOOL_OFFICE_DIRECTOR("校工会办公室主任"), SCHOOL_OFFICE_DIRECTOR("校工会办公室主任"),
SCHOOL_UNION_WELFARE_ADMIN("校工会福利管理员"), SCHOOL_UNION_WELFARE_ADMIN("校工会福利管理员"),
SCHOOL_UNION_MEMBER_ADMIN("校工会会员管理员"), SCHOOL_UNION_MEMBER_ADMIN("校工会会员管理员"),
@@ -1,16 +1,10 @@
package com.budwk.app.base.utils; package com.budwk.app.base.utils;
import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.FileUtil;
import com.aspose.cells.FontConfigs;
import com.aspose.cells.PdfSaveOptions; import com.aspose.cells.PdfSaveOptions;
import com.aspose.cells.Workbook; import com.aspose.cells.Workbook;
import com.aspose.slides.FontsLoader;
import com.aspose.slides.LoadOptions;
import com.aspose.slides.Presentation; import com.aspose.slides.Presentation;
import com.aspose.words.Document; import com.aspose.words.Document;
import com.aspose.words.FontSettings;
import com.aspose.words.FontSourceBase;
import com.aspose.words.MemoryFontSource;
import com.aspose.words.SaveFormat; import com.aspose.words.SaveFormat;
import com.budwk.app.base.exception.BaseException; import com.budwk.app.base.exception.BaseException;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@@ -18,8 +12,6 @@ import lombok.extern.slf4j.Slf4j;
import java.io.File; import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
/** /**
* 文档转换 * 文档转换
@@ -27,81 +19,6 @@ import java.util.Arrays;
@Slf4j @Slf4j
public class OfficePlusUtil { public class OfficePlusUtil {
private static final String DEFAULT_FONT_NAME = "Noto Sans SC";
private static final String DEFAULT_FONT_RESOURCE = "/fonts/NotoSansSC-VF.ttf";
private static final Object FONT_CONFIG_LOCK = new Object();
private static volatile boolean fontConfigured;
/**
* 注册系统内置字体,并将其作为 Office 文档缺失字体或缺失字形时的默认回退字体。
* 原文档字体在服务器可用时仍优先使用,只有找不到原字体时才使用内置字体。
*/
private static void configureDefaultFont() {
if (fontConfigured) {
return;
}
synchronized (FONT_CONFIG_LOCK) {
if (fontConfigured) {
return;
}
byte[] fontData = loadDefaultFontData();
configureWordFont(fontData);
configureExcelFont(fontData);
FontsLoader.loadExternalFont(fontData);
fontConfigured = true;
}
}
/**
* 从 classpath 读取随应用打包的字体,避免依赖线上服务器安装字体。
*
* @return 默认字体的二进制数据
*/
private static byte[] loadDefaultFontData() {
try (InputStream inputStream = OfficePlusUtil.class.getResourceAsStream(DEFAULT_FONT_RESOURCE)) {
if (inputStream == null) {
throw new BaseException("系统内置默认字体不存在");
}
return inputStream.readAllBytes();
} catch (IOException e) {
log.error("读取系统内置默认字体失败", e);
throw new BaseException("读取系统内置默认字体失败");
}
}
/**
* 为 Word 转换保留已有字体源并追加内置字体,同时配置缺失字体和缺失字形回退。
*
* @param fontData 默认字体的二进制数据
*/
private static void configureWordFont(byte[] fontData) {
FontSettings fontSettings = FontSettings.getDefaultInstance();
FontSourceBase[] currentSources = fontSettings.getFontsSources();
FontSourceBase[] fontSources = Arrays.copyOf(currentSources, currentSources.length + 1);
fontSources[currentSources.length] = new MemoryFontSource(fontData);
fontSettings.setFontsSources(fontSources);
fontSettings.getSubstitutionSettings().getDefaultFontSubstitution().setDefaultFontName(DEFAULT_FONT_NAME);
try {
fontSettings.getFallbackSettings().loadNotoFallbackSettings();
} catch (Exception e) {
log.error("配置 Word 默认字体回退规则失败", e);
throw new BaseException("配置 Word 默认字体失败");
}
}
/**
* 为 Excel 转换追加内置字体,并将其设置为找不到原字体时的默认字体。
*
* @param fontData 默认字体的二进制数据
*/
private static void configureExcelFont(byte[] fontData) {
com.aspose.cells.FontSourceBase[] currentSources = FontConfigs.getFontSources();
com.aspose.cells.FontSourceBase[] fontSources = Arrays.copyOf(currentSources, currentSources.length + 1);
fontSources[currentSources.length] = new com.aspose.cells.MemoryFontSource(fontData);
FontConfigs.setFontSources(fontSources);
FontConfigs.setDefaultFontName(DEFAULT_FONT_NAME);
}
/** /**
* 转换 * 转换
* *
@@ -130,11 +47,7 @@ public class OfficePlusUtil {
public static void presentationConvertPdf(String sourcePath, String targetPath) { public static void presentationConvertPdf(String sourcePath, String targetPath) {
Presentation presentation = null; Presentation presentation = null;
try { try {
configureDefaultFont(); presentation = new Presentation(sourcePath);
LoadOptions loadOptions = new LoadOptions();
loadOptions.setDefaultRegularFont(DEFAULT_FONT_NAME);
loadOptions.setDefaultAsianFont(DEFAULT_FONT_NAME);
presentation = new Presentation(sourcePath, loadOptions);
presentation.save(targetPath, com.aspose.slides.SaveFormat.Pdf); presentation.save(targetPath, com.aspose.slides.SaveFormat.Pdf);
} catch (Exception e) { } catch (Exception e) {
log.info("转换pdf报错", e); log.info("转换pdf报错", e);
@@ -155,12 +68,10 @@ public class OfficePlusUtil {
public static void excelConvertPdf(String sourcePath, String targetPath) { public static void excelConvertPdf(String sourcePath, String targetPath) {
FileOutputStream fileOS = null; FileOutputStream fileOS = null;
try { try {
configureDefaultFont();
Workbook wb = new Workbook(sourcePath); Workbook wb = new Workbook(sourcePath);
fileOS = new FileOutputStream(targetPath); fileOS = new FileOutputStream(targetPath);
PdfSaveOptions pdfSaveOptions = new PdfSaveOptions(); PdfSaveOptions pdfSaveOptions = new PdfSaveOptions();
pdfSaveOptions.setOnePagePerSheet(true); pdfSaveOptions.setOnePagePerSheet(true);
pdfSaveOptions.setDefaultFont(DEFAULT_FONT_NAME);
// for (int i = 0; i < 3; i++) { // for (int i = 0; i < 3; i++) {
// wb.getWorksheets().get(i).getHorizontalPageBreaks().clear(); // wb.getWorksheets().get(i).getHorizontalPageBreaks().clear();
// wb.getWorksheets().get(i).getVerticalPageBreaks().clear(); // wb.getWorksheets().get(i).getVerticalPageBreaks().clear();
@@ -194,7 +105,6 @@ public class OfficePlusUtil {
public static void wordConvertPdf(String sourcePath, String targetPath) { public static void wordConvertPdf(String sourcePath, String targetPath) {
FileOutputStream os = null; FileOutputStream os = null;
try { try {
configureDefaultFont();
File file = new File(targetPath); File file = new File(targetPath);
os = new FileOutputStream(file); os = new FileOutputStream(file);
Document doc = new Document(sourcePath); Document doc = new Document(sourcePath);
@@ -0,0 +1,54 @@
package com.budwk.app.flow.handler;
import com.budwk.app.base.constant.RoleConstant;
import com.budwk.app.flow.engine.AssignmentHandler;
import com.budwk.app.flow.engine.core.Execution;
import com.budwk.app.flow.engine.core.ServiceContext;
import com.budwk.app.flow.engine.model.TaskModel;
import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import java.util.List;
/**
* 为流程任务分配拥有校工会财务角色的用户。
*/
public class FlowSchoolUnionFinanceHandler implements AssignmentHandler {
/**
* 按校工会财务角色查询参与者,并去除重复的用户 ID。
*
* @param model 当前流程任务模型,无需额外传入人员参数
* @param execution 当前流程执行上下文,无需额外传入业务参数
* @return 用户 ID 列表;未配置角色或角色用户时返回空列表
*/
@Override
public List<String> assign(TaskModel model, Execution execution) {
Dao dao = ServiceContext.find(Dao.class);
Sql sql = Sqls.create("""
SELECT
userRole.userId
FROM
`sys_user_role` userRole
LEFT JOIN sys_role role ON role.id = userRole.roleId
WHERE
role.`code` = @roleCode
GROUP BY
userRole.userId
""").setParam("roleCode", RoleConstant.SCHOOL_UNION_FINANCE.name());
sql.setCallback(Sqls.callback.strList());
dao.execute(sql);
return sql.getList(String.class);
}
/**
* 返回流程设计器中展示的参与者处理类名称。
*
* @return 流程设计器下拉选项名称
*/
@Override
public String getMessage() {
return "获取校工会财务";
}
}
@@ -4,14 +4,12 @@ package com.budwk.app.zhgh.activity.sports.service.impl;
import cn.afterturn.easypoi.excel.ExcelExportUtil; import cn.afterturn.easypoi.excel.ExcelExportUtil;
import cn.afterturn.easypoi.excel.entity.ExportParams; import cn.afterturn.easypoi.excel.entity.ExportParams;
import cn.afterturn.easypoi.excel.entity.enmus.ExcelType; 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.service.impl.BaseServiceImpl;
import com.budwk.app.base.utils.CommonDownloadUtil; import com.budwk.app.base.utils.CommonDownloadUtil;
import com.budwk.app.base.utils.DateUtil; import com.budwk.app.base.utils.DateUtil;
import com.budwk.app.sys.models.Sys_home_activity; import com.budwk.app.sys.models.Sys_home_activity;
import com.budwk.app.sys.models.Sys_home_template; import com.budwk.app.sys.models.Sys_home_template;
import com.budwk.app.web.commons.auth.utils.SecurityUtil; 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.ActivityBasicUnit;
import com.budwk.app.zhgh.activity.basic.models.ActivityBasicUnion; import com.budwk.app.zhgh.activity.basic.models.ActivityBasicUnion;
import com.budwk.app.zhgh.activity.culture.models.ActivityTissue; import com.budwk.app.zhgh.activity.culture.models.ActivityTissue;
@@ -30,7 +28,6 @@ import org.nutz.dao.entity.Record;
import org.nutz.dao.sql.Sql; import org.nutz.dao.sql.Sql;
import org.nutz.ioc.aop.Aop; import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.IocBean; import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.json.Json;
import org.nutz.lang.Lang; import org.nutz.lang.Lang;
import org.nutz.lang.Strings; import org.nutz.lang.Strings;
@@ -58,7 +55,6 @@ public class ActivitySportsServiceImpl extends BaseServiceImpl<ActivitySchool> i
@Override @Override
@Aop(TransAop.READ_COMMITTED) @Aop(TransAop.READ_COMMITTED)
public String doAdd(ActivitySchool activitySchool, ActivitySchoolEvent[] events) { public String doAdd(ActivitySchool activitySchool, ActivitySchoolEvent[] events) {
events = validateAndApplySingleEventLimits(events);
ActivityBasicUnit activityBasicUnit = dao().fetch(ActivityBasicUnit.class, Cnd.where("id", "=", SecurityUtil.getUserId())); ActivityBasicUnit activityBasicUnit = dao().fetch(ActivityBasicUnit.class, Cnd.where("id", "=", SecurityUtil.getUserId()));
if (activitySchool.getActivityLevel() == 40002) { if (activitySchool.getActivityLevel() == 40002) {
@@ -105,7 +101,6 @@ public class ActivitySportsServiceImpl extends BaseServiceImpl<ActivitySchool> i
@Override @Override
@Aop(TransAop.READ_COMMITTED) @Aop(TransAop.READ_COMMITTED)
public void doEdit(ActivitySchool activitySchool, ActivitySchoolEvent[] events) { public void doEdit(ActivitySchool activitySchool, ActivitySchoolEvent[] events) {
events = validateAndApplySingleEventLimits(events);
ActivitySchool school = dao().fetch(ActivitySchool.class, activitySchool.getId()); ActivitySchool school = dao().fetch(ActivitySchool.class, activitySchool.getId());
if (school.getActivityLevel() == 40002) { if (school.getActivityLevel() == 40002) {
school.setBelongUnionId(SecurityUtil.getUnionId()); school.setBelongUnionId(SecurityUtil.getUnionId());
@@ -166,56 +161,6 @@ public class ActivitySportsServiceImpl extends BaseServiceImpl<ActivitySchool> i
dao().insertOrUpdate(sysHomeActivity); 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) @Aop(TransAop.READ_COMMITTED)
private void addTeam(String schoolId, ActivitySchoolEvent[] events) { private void addTeam(String schoolId, ActivitySchoolEvent[] events) {
if (events.length > 0) { if (events.length > 0) {
@@ -326,19 +271,33 @@ public class ActivitySportsServiceImpl extends BaseServiceImpl<ActivitySchool> i
WHERE WHERE
app.activityId = @id app.activityId = @id
and app.status = 2 and app.status = 2
and (@filterUnion = 0 or app.activityUnionId = @unionId) $cnd
ORDER BY ORDER BY
ev.allName DESC, ev.allName DESC,
u.unionname DESC, u.unionname DESC,
app.`team` asc app.`team` asc
""").setParam("id", id); """).setParam("id", id)
// 参数必须声明在原始SQL中;setVar追加的字符串不会再次解析其中的@参数。 .setVar("cnd", "");
// 未选择分工会时导出整个活动,选择时按报名记录中的活动分工会过滤。 if (Strings.isNotBlank(unionId)) {
sql.setParam("filterUnion", Strings.isNotBlank(unionId) ? 1 : 0); sql.setVar("cnd", "and u.unionid = @unionId");
sql.setParam("unionId", Strings.isNotBlank(unionId) ? unionId : ""); sql.setParam("unionId", unionId);
}
List<Record> list = list(sql); 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<ActivitySchoolApply> excels = new ArrayList<>();
list.forEach(record -> excels.add(toActivitySchoolApplyExport(record)));
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);
});
ExportParams exportParams = new ExportParams(); ExportParams exportParams = new ExportParams();
exportParams.setType(ExcelType.XSSF); exportParams.setType(ExcelType.XSSF);
@@ -346,42 +305,10 @@ public class ActivitySportsServiceImpl extends BaseServiceImpl<ActivitySchool> i
CommonDownloadUtil.download(getUnionApplyExportFileName(unionId), workbook, response); CommonDownloadUtil.download(getUnionApplyExportFileName(unionId), workbook, response);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); 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) { private String getUnionApplyExportFileName(String unionId) {
String unionName = "分工会"; String unionName = "分工会";
if (Strings.isNotBlank(unionId)) { if (Strings.isNotBlank(unionId)) {
@@ -17,6 +17,7 @@ import com.budwk.app.flow.enums.ProcessInstanceStateEnum;
import com.budwk.app.flow.enums.ProcessSubmitTypeEnum; import com.budwk.app.flow.enums.ProcessSubmitTypeEnum;
import com.budwk.app.flow.service.FlowCommonService; import com.budwk.app.flow.service.FlowCommonService;
import com.budwk.app.web.commons.auth.utils.SecurityUtil; 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.SysClub;
import com.budwk.app.zhgh.club.model.SysClubExamineRegister; import com.budwk.app.zhgh.club.model.SysClubExamineRegister;
import com.budwk.app.zhgh.club.service.SysClubExamineService; import com.budwk.app.zhgh.club.service.SysClubExamineService;
@@ -118,7 +119,10 @@ public class ClubExamineApplyController {
if(StrUtil.isBlank(clubId)) { if(StrUtil.isBlank(clubId)) {
return Result.error("社团信息为空,请核查"); return Result.error("社团信息为空,请核查");
} }
return Result.success(sysClubExamineService.getClubMemberMoney(clubId)); 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);
} }
@At @At
@@ -171,10 +175,6 @@ public class ClubExamineApplyController {
public Result save(@Param("data") SysClubExamineRegister examineRegister, public Result save(@Param("data") SysClubExamineRegister examineRegister,
@Param("incomeDetailed") String incomeDetailed, @Param("incomeDetailed") String incomeDetailed,
@Param("incomeCensus") String incomeCensus) { @Param("incomeCensus") String incomeCensus) {
String validateMessage = sysClubExamineService.validateActivityRequired(examineRegister);
if (validateMessage != null) {
return Result.error(validateMessage);
}
if (StrUtil.isBlank(examineRegister.getId())) { if (StrUtil.isBlank(examineRegister.getId())) {
sysClubExamineService.doAdd(examineRegister, incomeDetailed, incomeCensus); sysClubExamineService.doAdd(examineRegister, incomeDetailed, incomeCensus);
} else { } else {
@@ -190,10 +190,6 @@ public class ClubExamineApplyController {
public Result submit(@Param("data") SysClubExamineRegister examineRegister, public Result submit(@Param("data") SysClubExamineRegister examineRegister,
@Param("incomeDetailed") String incomeDetailed, @Param("incomeDetailed") String incomeDetailed,
@Param("incomeCensus") String incomeCensus) { @Param("incomeCensus") String incomeCensus) {
String validateMessage = sysClubExamineService.validateActivityRequired(examineRegister);
if (validateMessage != null) {
return Result.error(validateMessage);
}
SysClubExamineRegister reg; SysClubExamineRegister reg;
if (StrUtil.isBlank(examineRegister.getId())) { if (StrUtil.isBlank(examineRegister.getId())) {
reg = sysClubExamineService.doAdd(examineRegister, incomeDetailed, incomeCensus); reg = sysClubExamineService.doAdd(examineRegister, incomeDetailed, incomeCensus);
@@ -225,10 +221,6 @@ public class ClubExamineApplyController {
@Param("incomeDetailed") String incomeDetailed, @Param("incomeDetailed") String incomeDetailed,
@Param("incomeCensus") String incomeCensus, @Param("incomeCensus") String incomeCensus,
@Param("taskId") Long taskId) { @Param("taskId") Long taskId) {
String validateMessage = sysClubExamineService.validateActivityRequired(examineRegister);
if (validateMessage != null) {
return Result.error(validateMessage);
}
if (StrUtil.isBlank(examineRegister.getId())) { if (StrUtil.isBlank(examineRegister.getId())) {
sysClubExamineService.doAdd(examineRegister, incomeDetailed, incomeCensus); sysClubExamineService.doAdd(examineRegister, incomeDetailed, incomeCensus);
} else { } else {
@@ -86,7 +86,7 @@ public class ClubExamineMineController {
//nutMap.setv("schoolName", Globals.MyConfig.getString("AppName")); //nutMap.setv("schoolName", Globals.MyConfig.getString("AppName"));
nutMap.setv("clubName", register.getClubName()); nutMap.setv("clubName", register.getClubName());
nutMap.setv("create_time", register.getFoundTime()); nutMap.setv("create_time", register.getFoundTime());
nutMap.setv("dues_standard", register.getDue() == null ? "--" : register.getDue() + "元/年"); nutMap.setv("dues_standard", register.getDue());
// 生成材料中的年度与列表展示的登记年度保持一致。 // 生成材料中的年度与列表展示的登记年度保持一致。
nutMap.setv("year", register.getYear()); nutMap.setv("year", register.getYear());
nutMap.setv("registerDate", register.getRegisterDate()); nutMap.setv("registerDate", register.getRegisterDate());
@@ -91,8 +91,8 @@ public class SysClub extends BaseModel {
@Column @Column
@Comment("会费标准") @Comment("会费标准")
@ColDefine(type = ColType.DOUBLE, width = 10, precision = 2) @ColDefine(type = ColType.VARCHAR, width = 50)
private Double due; private String due;
@Column @Column
@Comment("附件") @Comment("附件")
@@ -57,8 +57,8 @@ public class SysClubExamineRegister extends BaseModel {
@Column @Column
@Comment("会费标准") @Comment("会费标准")
@ColDefine(type = ColType.DOUBLE, width = 10, precision = 2) @ColDefine(type = ColType.VARCHAR, width = 32)
private Double due; private String due;
@Column @Column
@Comment("年度活动情况") @Comment("年度活动情况")
@@ -24,21 +24,6 @@ public interface SysClubExamineService extends BaseService<SysClubExamineRegiste
List<NutMap> getJgUser(@Valid String clubId); 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 doAdd(SysClubExamineRegister examineRegister, String incomeDetailed, String incomeCensus);
SysClubExamineRegister doEdit(SysClubExamineRegister examineRegister, String incomeDetailed, String incomeCensus); SysClubExamineRegister doEdit(SysClubExamineRegister examineRegister, String incomeDetailed, String incomeCensus);
@@ -13,7 +13,6 @@ import com.budwk.app.flow.enums.ProcessInstanceStateEnum;
import com.budwk.app.flow.enums.ProcessTaskStateEnum; import com.budwk.app.flow.enums.ProcessTaskStateEnum;
import com.budwk.app.web.commons.auth.utils.AuthUtil; import com.budwk.app.web.commons.auth.utils.AuthUtil;
import com.budwk.app.web.commons.auth.utils.SecurityUtil; 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.SysClub;
import com.budwk.app.zhgh.club.model.SysClubExamineRegister; import com.budwk.app.zhgh.club.model.SysClubExamineRegister;
import com.budwk.app.zhgh.club.model.SysClubExamineRegisterDetailed; import com.budwk.app.zhgh.club.model.SysClubExamineRegisterDetailed;
@@ -51,15 +50,6 @@ public class SysClubExamineServiceImpl extends BaseServiceImpl<SysClubExamineReg
super(dao); 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 @Override
public List<NutMap> getClubUserNum(String clubId, Integer year) { public List<NutMap> getClubUserNum(String clubId, Integer year) {
Date yearStart = DateUtil.parse(year + "-01-01 00:00:00"); Date yearStart = DateUtil.parse(year + "-01-01 00:00:00");
@@ -355,40 +345,6 @@ public class SysClubExamineServiceImpl extends BaseServiceImpl<SysClubExamineReg
return listMap; 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 @Override
public SysClubExamineRegister doAdd(SysClubExamineRegister examineRegister, String incomeDetailed, String incomeCensus) { public SysClubExamineRegister doAdd(SysClubExamineRegister examineRegister, String incomeDetailed, String incomeCensus) {
// JSON转换为实体列表(空值判断避免转换异常) // JSON转换为实体列表(空值判断避免转换异常)
@@ -229,14 +229,18 @@ public class ProposalMineController {
} }
/**
* 删除当前用户可删除的提案。
*
* @param id 提案 ID;仅允许删除本人未发起流程或退回发起节点的提案
* @return Result,成功时返回成功结果,不满足删除条件时返回原因
*/
@At @At
@SaCheckPermission("proposal.mine") @SaCheckPermission("proposal.mine")
@Aop(TransAop.READ_COMMITTED) @Aop(TransAop.READ_COMMITTED)
@SLog(tag = "提案管理系统-我的提案", msg = "删除提案") @SLog(tag = "提案管理系统-我的提案", msg = "删除提案")
public Result delete(@Valid String id) { public Result delete(@Valid String id) {
dao.delete(ProposalInfo.class, id); return proposalCommonService.deleteMine(id, SecurityUtil.getUserId());
flowEngine.processInstanceService().deleteProcessInstanceByBusinessKey(id);
return Result.success();
} }
@At @At
@@ -3,6 +3,7 @@ package com.budwk.app.zhgh.democratic.proposal.service.common;
import com.budwk.app.base.service.BaseService; import com.budwk.app.base.service.BaseService;
import com.budwk.app.base.param.PageForm; import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.param.ExportTableColumns; import com.budwk.app.base.param.ExportTableColumns;
import com.budwk.app.base.result.Result;
import com.budwk.app.zhgh.democratic.proposal.models.ProposalInfo; import com.budwk.app.zhgh.democratic.proposal.models.ProposalInfo;
import com.budwk.app.zhgh.democratic.proposal.models.ProposalUndertake; import com.budwk.app.zhgh.democratic.proposal.models.ProposalUndertake;
import com.budwk.app.zhgh.democratic.proposal.param.ProposalQueryComprehensiveParam; import com.budwk.app.zhgh.democratic.proposal.param.ProposalQueryComprehensiveParam;
@@ -19,6 +20,15 @@ import java.util.Set;
public interface ProposalCommonService extends BaseService<ProposalInfo> { public interface ProposalCommonService extends BaseService<ProposalInfo> {
/**
* 删除当前用户尚未发起流程或已退回发起节点的提案。
*
* @param id 待删除的提案 ID
* @param userId 当前登录用户 ID,用于核对提案创建人
* @return Result,删除成功返回 success;提案不存在、非本人或流程不允许删除时返回错误信息
*/
Result deleteMine(String id, String userId);
/** /**
* 按页面允许的字段白名单添加排序条件,避免前端字段直接进入SQL。 * 按页面允许的字段白名单添加排序条件,避免前端字段直接进入SQL。
* *
@@ -17,6 +17,7 @@ import com.budwk.app.base.exception.BaseException;
import com.budwk.app.base.param.ExportTableColumns; import com.budwk.app.base.param.ExportTableColumns;
import com.budwk.app.base.param.PageForm; import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.page.Pagination; import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.result.Result;
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.CommonDownloadUtil;
import com.budwk.app.base.utils.PageUtil; import com.budwk.app.base.utils.PageUtil;
@@ -52,6 +53,7 @@ import com.deepoove.poi.config.Configure;
import com.deepoove.poi.plugin.table.LoopRowTableRenderPolicy; import com.deepoove.poi.plugin.table.LoopRowTableRenderPolicy;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.ddr.poi.html.HtmlRenderPolicy; import org.ddr.poi.html.HtmlRenderPolicy;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.dao.Cnd; import org.nutz.dao.Cnd;
import org.nutz.dao.Dao; import org.nutz.dao.Dao;
import org.nutz.dao.FieldFilter; import org.nutz.dao.FieldFilter;
@@ -62,6 +64,7 @@ import org.nutz.dao.util.cri.SqlExpressionGroup;
import org.nutz.dao.util.cri.Static; import org.nutz.dao.util.cri.Static;
import org.nutz.ioc.loader.annotation.Inject; import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean; import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.ioc.aop.Aop;
import org.nutz.json.Json; import org.nutz.json.Json;
import org.nutz.lang.Lang; import org.nutz.lang.Lang;
import org.nutz.lang.Strings; import org.nutz.lang.Strings;
@@ -184,6 +187,45 @@ public class ProposalCommonServiceImpl extends BaseServiceImpl<ProposalInfo> imp
super(dao); super(dao);
} }
/**
* 仅允许创建人删除草稿或退回发起节点的提案,并清理其 WF 流程。
*
* @param id 待删除的提案 ID
* @param userId 当前登录用户 ID,用于核对提案创建人
* @return Result,删除成功返回 success;提案不存在、非本人或流程不允许删除时返回错误信息
*/
@Override
@Aop(TransAop.READ_COMMITTED)
public Result deleteMine(String id, String userId) {
if (StrUtil.isBlank(id) || StrUtil.isBlank(userId)) {
return Result.error("提案不存在或无权删除");
}
ProposalInfo proposal = dao().fetch(ProposalInfo.class, id);
if (proposal == null || !Objects.equals(proposal.getCreatedBy(), userId)) {
return Result.error("提案不存在或无权删除");
}
List<ProcessInstance> instances = dao().query(ProcessInstance.class,
Cnd.where(ProcessInstance::getBusinessNo, "=", id));
// 一个提案出现多个流程实例时无法安全判断当前节点,拒绝删除并保留数据。
if (instances.size() > 1) {
return Result.error("提案流程数据异常,无法删除");
}
if (!instances.isEmpty()) {
ProcessInstance instance = instances.get(0);
List<ProcessTask> tasks = flowEngine.processTaskService().getDoingTaskList(instance.getId(), null);
// 存在并行任务时,只有所有活动任务均位于发起节点才允许删除。
if (!Objects.equals(instance.getState(), ProcessInstanceStateEnum.DOING.getCode())
|| tasks.isEmpty() || tasks.stream().anyMatch(task -> !Objects.equals(task.getTaskName(), "startTask"))) {
return Result.error("仅未提交或退回发起节点的提案可以删除");
}
}
dao().delete(ProposalInfo.class, id);
flowEngine.processInstanceService().deleteProcessInstanceByBusinessKey(id);
return Result.success();
}
/** /**
* 分页查询提案综合列表。 * 分页查询提案综合列表。
* 普通查询不关联历史任务表;仅在 origins 包含 answer 且 commonKeyword 非空时, * 普通查询不关联历史任务表;仅在 origins 包含 answer 且 commonKeyword 非空时,
@@ -101,7 +101,7 @@ public class TeacherCongressInstitutionUserServiceImpl extends BaseServiceImpl<T
if (identity.equals("主任")) { if (identity.equals("主任")) {
sysRole = sysRoleService.getByCode(RoleConstant.TEACHER_CONGRESS_PREPARATION_LEADERSHIP_GROUP_DIRECTOR); sysRole = sysRoleService.getByCode(RoleConstant.TEACHER_CONGRESS_PREPARATION_LEADERSHIP_GROUP_DIRECTOR);
} else if (identity.equals("成员")) { } else if (identity.equals("委员")) {
sysRole = sysRoleService.getByCode(RoleConstant.TEACHER_CONGRESS_PREPARATION_LEADERSHIP_GROUP_MEMBER); sysRole = sysRoleService.getByCode(RoleConstant.TEACHER_CONGRESS_PREPARATION_LEADERSHIP_GROUP_MEMBER);
} }
if (sysRole == null) { if (sysRole == null) {
@@ -148,7 +148,7 @@ public class TeacherCongressInstitutionUserServiceImpl extends BaseServiceImpl<T
if (institutionUser.getIdentity().equals("主任")) { if (institutionUser.getIdentity().equals("主任")) {
sysRole = sysRoleService.getByCode(RoleConstant.TEACHER_CONGRESS_PREPARATION_LEADERSHIP_GROUP_DIRECTOR); sysRole = sysRoleService.getByCode(RoleConstant.TEACHER_CONGRESS_PREPARATION_LEADERSHIP_GROUP_DIRECTOR);
} else if (institutionUser.getIdentity().equals("成员")) { } else if (institutionUser.getIdentity().equals("委员")) {
sysRole = sysRoleService.getByCode(RoleConstant.TEACHER_CONGRESS_PREPARATION_LEADERSHIP_GROUP_MEMBER); sysRole = sysRoleService.getByCode(RoleConstant.TEACHER_CONGRESS_PREPARATION_LEADERSHIP_GROUP_MEMBER);
} }
if (sysRole == null) { if (sysRole == null) {
+3 -3
View File
@@ -5,7 +5,7 @@ nutz:
ignore: ^(.+[.])(jsp|png|gif|jpg|js|css|jspx|jpeg|html|mp3|mp4|ico|svg|vue)$ ignore: ^(.+[.])(jsp|png|gif|jpg|js|css|jspx|jpeg|html|mp3|mp4|ico|svg|vue)$
exclusions: /favicon/*,/assets/*,/druid/*,/upload/*,/components/* exclusions: /favicon/*,/assets/*,/druid/*,/upload/*,/components/*
server: server:
port: 8081 port: 80
host: 0.0.0.0 host: 0.0.0.0
jetty: jetty:
@@ -21,7 +21,7 @@ jetty:
#结合ftp使用,或用nginx代理ftp路径 #结合ftp使用,或用nginx代理ftp路径
#staticPath: /Users/wizzer/temp/files #staticPath: /Users/wizzer/temp/files
#开发模式静态资源 #开发模式静态资源
staticPathLocal: D:/myProject/zhgh_cqgmy/src/main/resources/static staticPathLocal: E:/projects/zhgh_cqgmy/src/main/resources/static
security: security:
tokenName: saToken tokenName: saToken
@@ -87,7 +87,7 @@ xsssql:
beetl: beetl:
RESOURCE: RESOURCE:
#本地路径,开发时设置本地路径,便于调试 #本地路径,开发时设置本地路径,便于调试
rootLocal: D:/myProject/zhgh_cqgmy/src/main/resources/views/ rootLocal: E:/projects/zhgh_cqgmy/src/main/resources/views/
root: views/ root: views/
DELIMITER_STATEMENT_START: "<!--#" DELIMITER_STATEMENT_START: "<!--#"
DELIMITER_STATEMENT_END: "#-->" DELIMITER_STATEMENT_END: "#-->"
@@ -1,92 +0,0 @@
This Font Software is licensed under the SIL Open Font License,
Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font
creation efforts of academic and linguistic communities, and to
provide a free and open framework in which fonts may be shared and
improved in partnership with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply to
any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software
components as distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to,
deleting, or substituting -- in part or in whole -- any of the
components of the Original Version, by changing formats or by porting
the Font Software to a new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed,
modify, redistribute, and sell modified and unmodified copies of the
Font Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components, in
Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the
corresponding Copyright Holder. This restriction only applies to the
primary font name as presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created using
the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
Binary file not shown.
File diff suppressed because one or more lines are too long
@@ -380,7 +380,7 @@
</el-form> </el-form>
<span slot="footer" class="dialog-footer"> <span slot="footer" class="dialog-footer">
<el-button @click="setDialogVisible = false">取消</el-button> <el-button @click="setDialogVisible = false">取消</el-button>
<el-button type="primary" :disabled="settingLoading" @click="doSetActivityUser">确定</el-button> <el-button type="primary" @click="doSetActivityUser" v-loading="settingLoading">确定</el-button>
</span> </span>
</el-dialog> </el-dialog>
@@ -646,7 +646,7 @@ module.exports = {
doSearch(showFullLoading = false) { doSearch(showFullLoading = false) {
this.tableKey = new Date().getTime() this.tableKey = new Date().getTime()
this.pageForm.pageNumber = 1 this.pageForm.pageNumber = 1
return this.pageData(showFullLoading === true) this.pageData(showFullLoading === true)
}, },
async pageData(showFullLoading = false) { async pageData(showFullLoading = false) {
this.tableLoading = true this.tableLoading = true
@@ -679,74 +679,43 @@ module.exports = {
} }
} }
}, },
doSetActivityUser() { async doSetActivityUser() {
// 方法级状态保护与整页遮罩共同阻止慢请求期间的重复确认。 const valid = await this.$refs.setForm.validate()
if (this.settingLoading) { if (!valid) {
return return
} }
this.$refs.setForm.validate((valid) => { if (this.formData.groupType !== 2 && this.tableData.length === 0) {
if (!valid) { this.$message.warning("请先指定筛选条件!")
return return
} }
if (this.formData.groupType !== 2 && this.tableData.length === 0) { const confirm = await this.$confirm("是否将符合搜索条件的用户设为活动人员?", "提示", {
this.$message.warning("请先指定筛选条件!") confirmButtonText: "确定",
return cancelButtonText: "取消",
} type: "warning"
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()
})
})
}) })
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.settingLoading = false
this.$emit("update:group_id", resp.data)
this.$emit("group_change")
}
}, },
async getActivityGroup() { async getActivityGroup() {
const resp = await $.get("/platform/activity/basic/scope/getActivityUserScopeGroup") const resp = await $.get("/platform/activity/basic/scope/getActivityUserScopeGroup")
@@ -10,7 +10,7 @@
:finished="tableFinished" :finished="tableFinished"
finished-text="没有更多了" finished-text="没有更多了"
@load="onLoad"> @load="onLoad">
<div class="table-list-container"> <div class="table-list-container" @click="showOverflowText">
<div v-for="(row, index) in tableData" :key="index" class="table-list-item" <div v-for="(row, index) in tableData" :key="index" class="table-list-item"
:data-row-id="row && row.id ? row.id : ''"> :data-row-id="row && row.id ? row.id : ''">
<slot name="header" :index="index" :row="row"> <slot name="header" :index="index" :row="row">
@@ -36,6 +36,12 @@
</div> </div>
</div> </div>
</van-list> </van-list>
<van-popover ref="overflowPopover" :value="overflowVisible" :style="overflowAnchorStyle"
:placement="overflowPlacement" get-container="body" @input="onOverflowInput"
@opened="positionOverflow">
<div class="table-list-overflow-content">{{ overflowText }}</div>
<template #reference><span></span></template>
</van-popover>
</van-pull-refresh> </van-pull-refresh>
</template> </template>
<script> <script>
@@ -90,10 +96,138 @@ module.exports = {
tableLoading: false, tableLoading: false,
tableFinished: false, tableFinished: false,
tableRefreshing: false, tableRefreshing: false,
localPageForm: {...this.page_form} localPageForm: {...this.page_form},
overflowVisible: false,
overflowText: "",
overflowTarget: null,
overflowPlacement: "top",
overflowAnchorStyle: {}
} }
}, },
methods: { methods: {
// 只响应真正被截断的纯文字,保留卡片内按钮和业务插槽原有的点击行为。
showOverflowText(event) {
const target = event.target
if (!target || !target.closest) return
const card = target.closest(".table-list-item")
if (!card || target.closest(".item-actions, a, button, input, textarea, select, [role=button], [tabindex], [onclick]")) return
if (target.children.length || !target.textContent.trim() || this.hasOverflowClickHandler(target, card)) return
const style = window.getComputedStyle(target)
if (style.textOverflow !== "ellipsis" || (target.scrollWidth <= target.clientWidth && target.scrollHeight <= target.clientHeight)) return
const rect = target.getBoundingClientRect()
this.overflowTarget = target
this.overflowText = target.textContent.trim()
this.overflowPlacement = rect.top < 180 ? "bottom" : "top"
this.overflowAnchorStyle = {
position: "fixed",
display: "block",
pointerEvents: "none",
left: rect.left + "px",
top: rect.top + "px",
width: rect.width + "px",
height: rect.height + "px"
}
if (!this.overflowVisible) this.bindOverflowListeners()
this.overflowVisible = true
this.$nextTick(() => {
const instance = this.$refs.overflowPopover
if (instance) instance.updateLocation()
setTimeout(() => this.positionOverflow(), 50)
setTimeout(() => this.positionOverflow(), 350)
})
},
// 检查文字及其卡片内的祖先节点,避免干扰已有 Vue 点击事件。
hasOverflowClickHandler(target, card) {
const ancestors = []
let element = target
while (element && element !== card.parentElement) {
ancestors.push(element)
element = element.parentElement
}
const visit = (node) => {
if (!node) return false
const events = node.data ? [node.data.on, node.data.nativeOn] : []
if (ancestors.includes(node.elm) && events.some((handlers) => handlers &&
Object.keys(handlers).some((key) => /^[~!&]*(click|touchstart|pointerdown|mousedown)$/.test(key)))) return true
if (node.componentInstance && visit(node.componentInstance._vnode)) return true
return Array.isArray(node.children) && node.children.some((child) => visit(child))
}
return visit(this._vnode)
},
// Vant 的弹层挂在 body;校正窗口边缘的位置,避免完整文字被屏幕裁掉。
positionOverflow() {
const instance = this.$refs.overflowPopover
const popup = instance && instance.$refs.popover && instance.$refs.popover.$el
if (!this.overflowVisible || !this.overflowTarget || !popup) return
const rect = popup.getBoundingClientRect()
const anchor = this.overflowTarget.getBoundingClientRect()
if (!rect.width || !rect.height) return
const margin = 8
const left = Math.max(margin, Math.min(rect.left, window.innerWidth - rect.width - margin))
let top = rect.top
if (top < margin) top = anchor.bottom + 8
if (top + rect.height > window.innerHeight - margin) top = anchor.top - rect.height - 8
top = Math.max(margin, Math.min(top, window.innerHeight - rect.height - margin))
popup.style.left = (parseFloat(popup.style.left) || 0) + left - rect.left + "px"
popup.style.top = (parseFloat(popup.style.top) || 0) + top - rect.top + "px"
const arrow = popup.querySelector(".van-popover__arrow")
if (arrow) {
arrow.style.left = Math.max(12, Math.min(anchor.left + anchor.width / 2 - left, rect.width - 12)) + "px"
}
},
onOverflowInput(visible) {
this.overflowVisible = visible
if (!visible) {
this.overflowTarget = null
this.unbindOverflowListeners()
}
},
// 监听只在气泡打开时存在,PJAX 替换页面前也会关闭气泡并注销。
bindOverflowListeners() {
document.addEventListener("click", this.closeOverflowOnClick)
window.addEventListener("scroll", this.closeOverflowOnMove, true)
window.addEventListener("resize", this.closeOverflowOnMove)
window.addEventListener("transitionend", this.onOverflowAnimationEnd)
window.addEventListener("animationend", this.onOverflowAnimationEnd)
$(document).on("pjax:send", this.closeOverflowForNavigation)
},
unbindOverflowListeners() {
document.removeEventListener("click", this.closeOverflowOnClick)
window.removeEventListener("scroll", this.closeOverflowOnMove, true)
window.removeEventListener("resize", this.closeOverflowOnMove)
window.removeEventListener("transitionend", this.onOverflowAnimationEnd)
window.removeEventListener("animationend", this.onOverflowAnimationEnd)
$(document).off("pjax:send", this.closeOverflowForNavigation)
},
closeOverflowForNavigation() {
this.onOverflowInput(false)
},
// Vant 会在动画结束时重新计算定位,随后再次限制气泡的屏幕边界。
onOverflowAnimationEnd() {
if (this.overflowVisible) setTimeout(() => this.positionOverflow(), 0)
},
// 滚动或改变屏幕尺寸时关闭气泡,避免气泡脱离原文字位置。
closeOverflowOnMove(event) {
if (event && event.target && event.target.closest && event.target.closest(".table-list-overflow-content")) return
this.onOverflowInput(false)
},
closeOverflowOnClick(event) {
if (!this.overflowVisible) return
if (this.overflowTarget && this.overflowTarget.contains(event.target)) return
if (event.target.closest && event.target.closest(".table-list-overflow-content")) return
this.onOverflowInput(false)
},
/** /**
* 获取当前列表快照。 * 获取当前列表快照。
* 返回值包含已加载记录、分页参数和加载完成状态,供业务页面在进入详情前暂存列表现场。 * 返回值包含已加载记录、分页参数和加载完成状态,供业务页面在进入详情前暂存列表现场。
@@ -186,10 +320,28 @@ module.exports = {
}, },
mounted() { mounted() {
this.$emit("ready") this.$emit("ready")
},
beforeDestroy() {
this.unbindOverflowListeners()
} }
} }
</script> </script>
<style>
/* Vant 将气泡挂到 body,控制宽高并允许长文本在气泡内滚动。 */
.table-list-overflow-content {
max-width: min(240px, calc(100vw - 48px));
max-height: 160px;
padding: 10px 12px;
overflow-y: auto;
color: #253247;
font-size: 13px;
line-height: 1.55;
white-space: pre-wrap;
overflow-wrap: anywhere;
}
</style>
<style scoped> <style scoped>
.table-list-container { .table-list-container {
padding: 0; padding: 0;
@@ -52,7 +52,6 @@ module.exports = {
*/ */
init() { init() {
this.signatureAddress = origin + "/platform/signature/scanPcCode?id=" + this.id this.signatureAddress = origin + "/platform/signature/scanPcCode?id=" + this.id
console.log(this.signatureAddress)
return this.$axios.post("/platform/signature/get") return this.$axios.post("/platform/signature/get")
.then((res) => { .then((res) => {
if (this._isBeingDestroyed || this._isDestroyed) { if (this._isBeingDestroyed || this._isDestroyed) {
@@ -706,7 +706,7 @@
Vue.component("year-van-dropdown-item", httpVueLoader("/components/plugins/vantMore/yearVanDropDownItem.vue?v=" + h5ComponentVersion)) Vue.component("year-van-dropdown-item", httpVueLoader("/components/plugins/vantMore/yearVanDropDownItem.vue?v=" + h5ComponentVersion))
Vue.component("van-text-dialog", httpVueLoader("/components/plugins/vantMore/vantTextDialog.vue?v=" + h5ComponentVersion)) Vue.component("van-text-dialog", httpVueLoader("/components/plugins/vantMore/vantTextDialog.vue?v=" + h5ComponentVersion))
Vue.component("enum-tag", httpVueLoader("/components/plugins/sysEnum/EnumTag.vue?v=" + h5ComponentVersion)) Vue.component("enum-tag", httpVueLoader("/components/plugins/sysEnum/EnumTag.vue?v=" + h5ComponentVersion))
Vue.component("table-list", httpVueLoader("/components/plugins/h5/TableList.vue?v=" + h5ComponentVersion)) Vue.component("table-list", httpVueLoader("/components/plugins/h5/TableList.vue?v=" + h5ComponentVersion + "_overflow1"))
Vue.component("table-column", httpVueLoader("/components/plugins/h5/TableColumn.vue?v=" + h5ComponentVersion)) Vue.component("table-column", httpVueLoader("/components/plugins/h5/TableColumn.vue?v=" + h5ComponentVersion))
Vue.component("file-preview", httpVueLoader("/components/plugins/sysFilePreview/H5Index.vue?v=" + h5ComponentVersion)) Vue.component("file-preview", httpVueLoader("/components/plugins/sysFilePreview/H5Index.vue?v=" + h5ComponentVersion))
</script> </script>
@@ -75,7 +75,7 @@
<!-- 引入 form-create 和 designer --> <!-- 引入 form-create 和 designer -->
<script src="${base!}/assets/platform/plugins/form-create/form-create.min.js"></script> <script src="${base!}/assets/platform/plugins/form-create/form-create.min.js"></script>
<script src="${base!}/assets/platform/plugins/form-create/index.umd.js?v=20261030"></script> <script src="${base!}/assets/platform/plugins/form-create/index.umd.js"></script>
<!-- swiper --> <!-- swiper -->
<script src="${base!}/assets/platform/plugins/swiper/swiper-bundle.js"></script> <script src="${base!}/assets/platform/plugins/swiper/swiper-bundle.js"></script>
@@ -139,7 +139,7 @@ const act = {
} }
if (act.url === "/platform/activity/apply" if (act.url === "/platform/activity/apply"
&& !this.$auth.hasRoleOr(["SYSADMIN", "SCHOOL_UNION_ADMIN", "BRANCH_UNION_CHAIRMAN", "BRANCH_UNION_WENTI_SPORTS"])) { && !this.$auth.hasRoleOr(["SYSADMIN", "SCHOOL_UNION_ADMIN", "BRANCH_UNION_CHAIRMAN", "BRANCH_UNION_WENTI_SPORTS"])) {
return this.$message.warning("温馨提示:您没有参加该活动的权限!") return this.$message.warning("PC端暂未开放报名,请前往移动端报名!")
} }
window.open(act.url) window.open(act.url)
@@ -116,69 +116,6 @@ layout("/layouts/v4/baseLayout.html"){
object-fit: contain; 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> </style>
<div class="v4-container" id="v4-home-app"> <div class="v4-container" id="v4-home-app">
@@ -100,7 +100,7 @@ layout("/layouts/platform.html"){
size="mini" type="danger">删除 size="mini" type="danger">删除
</el-button> </el-button>
<el-button @click="doExportDeclare(row)" size="mini" type="primary">申请表导出</el-button> <el-button v-if="row.instanceState === 20" @click="doExportDeclare(row)" size="mini" type="primary">申请表导出</el-button>
<el-button v-if="row.instanceState === 20" @click="doExportReimbursement(row)" size="mini" <el-button v-if="row.instanceState === 20" @click="doExportReimbursement(row)" size="mini"
type="primary">报销凭证导出 type="primary">报销凭证导出
</el-button> </el-button>
@@ -221,7 +221,8 @@ layout("/layouts/platform.html"){
placeholder="请输入姓名" filterable clearable placeholder="请输入姓名" filterable clearable
style="width: 100%" size="small" v-if="awardsMode==1"> style="width: 100%" size="small" v-if="awardsMode==1">
<el-option <el-option
v-for="item in getSelectableUserList(row)" v-for="item in userList"
:disabled="item.disabled"
:key="item.id" :key="item.id"
:label="item.username+'('+item.loginname+')'+item.sex" :label="item.username+'('+item.loginname+')'+item.sex"
:value="item.id"> :value="item.id">
@@ -720,15 +721,10 @@ layout("/layouts/platform.html"){
} }
return score; 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) { userDetailsChange(val, row) {
const useridArr = this.userData.map(v => v.userId)
this.userList.forEach(v => { this.userList.forEach(v => {
v.disabled = useridArr.includes(v.id)
if (row && row.userId === v.id) { if (row && row.userId === v.id) {
const o = { const o = {
unionId: v.unionId, unionId: v.unionId,
@@ -601,8 +601,6 @@
eventForm: { eventForm: {
isLeader: false isLeader: false
}, },
// 独立记录当前编辑项目,避免下拉框切换后丢失切换前项目标识。
currentEventId: "",
eventsIds: [], eventsIds: [],
rightChecked: [], rightChecked: [],
checkedEventInfo: [], checkedEventInfo: [],
@@ -780,10 +778,8 @@
this.checkedEventInfo = this.checkedEvents.map((v) => { this.checkedEventInfo = this.checkedEvents.map((v) => {
v.eventId = v.id v.eventId = v.id
if (v.projectType === "1") { if (v.projectType === "1") {
// “所有单项”保存默认值,具体单项以0表示尚未个性化设置。 v.athletesMaxNum = 3
const isAllItems = v.eventId === "allItems" v.restrictRegNumber = 2
v.athletesMaxNum = isAllItems ? 3 : 0
v.restrictRegNumber = isAllItems ? 2 : 0
v.leanderNum = 0 v.leanderNum = 0
v.coachNum = 0 v.coachNum = 0
v.applyUserModel = 1 v.applyUserModel = 1
@@ -795,23 +791,7 @@
return v 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) { async eventChange(val) {
this.cacheCurrentEventForm()
const data = this.checkedEventInfo.find((v) => v.eventId === val) const data = this.checkedEventInfo.find((v) => v.eventId === val)
const resp = await $.get("/platform/activity/sports/common/getEventData", { id: val }) const resp = await $.get("/platform/activity/sports/common/getEventData", { id: val })
await this.$nextTick() await this.$nextTick()
@@ -824,10 +804,10 @@
projectType: resp.data.projectType, projectType: resp.data.projectType,
leanderNum: 0, leanderNum: 0,
coachNum: 0, coachNum: 0,
athletesMaxNum: resp.data.projectType === 1 ? 0 : null, athletesMaxNum: resp.data.projectType === 1 ? 3 : null,
restrictTotalTeam: 1, restrictTotalTeam: 1,
substituteNum: 0, substituteNum: 0,
restrictRegNumber: resp.data.projectType === 1 ? 0 : null, restrictRegNumber: resp.data.projectType === 1 ? 2 : null,
isLeader: false, isLeader: false,
applyUserModel: 1 applyUserModel: 1
} }
@@ -835,27 +815,33 @@
this.projectType = resp.data.projectType this.projectType = resp.data.projectType
} }
this.$set(this.eventForm, "checkedEventId", val) this.$set(this.eventForm, "checkedEventId", val)
this.currentEventId = val
}, },
saveEvent() { async saveEvent() {
const data = clone(this.eventForm) const data = clone(this.eventForm)
if (data.checkedEventId === "allItems") { if (data.checkedEventId === "allItems") {
const allItemsIndex = this.checkedEventInfo.findIndex((v) => v.eventId === "allItems") this.checkedEventInfo.forEach((v) => {
if (allItemsIndex >= 0) { if (v.projectType === "1") {
this.checkedEventInfo.splice(allItemsIndex, 1, data) v.applyUserModel = this.eventForm.applyUserModel
} v.athletesMaxNum = data.athletesMaxNum
v.restrictRegNumber = data.restrictRegNumber
v.unionLimit = data.unionLimit
}
})
} else { } else {
const info = this.checkedEventInfo.find((v) => v.eventId === data.checkedEventId) const info = this.checkedEventInfo.find((v) => v.eventId === data.checkedEventId)
if (info != null) { if (info != null) {
const index = this.checkedEventInfo.findIndex((v) => v.eventId === data.checkedEventId) const index = this.checkedEventInfo.findIndex((v) => v.eventId === data.checkedEventId)
this.checkedEventInfo.splice(index, 1) 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 data.eventId = data.checkedEventId
this.checkedEventInfo.push(data) this.checkedEventInfo.push(data)
} }
this.eventForm = clone(data) await this.doSave()
this.currentEventId = data.checkedEventId
return this.doSave()
}, },
prev() { prev() {
this.active-- this.active--
@@ -963,7 +949,6 @@
this.copyTemplateMode = false this.copyTemplateMode = false
this.copyTemplateName = "" this.copyTemplateName = ""
this.pushTodoFlag = false this.pushTodoFlag = false
this.currentEventId = ""
this.events = await this.getEvents(2) this.events = await this.getEvents(2)
await this.getActivityGroup() await this.getActivityGroup()
this.active = 0 this.active = 0
@@ -1043,7 +1028,6 @@
async openEdit(row, flag, copyTemplateMode = false) { async openEdit(row, flag, copyTemplateMode = false) {
this.copyTemplateMode = !!copyTemplateMode this.copyTemplateMode = !!copyTemplateMode
this.pushTodoFlag = false this.pushTodoFlag = false
this.currentEventId = ""
this.active = flag ? 0 : this.active this.active = flag ? 0 : this.active
await this.getActivityGroup() await this.getActivityGroup()
const { id } = row const { id } = row
@@ -1200,8 +1184,7 @@
} }
if (this.eventForm.checkedEventId == "allItems") { if (this.eventForm.checkedEventId == "allItems") {
// “所有单项”配置随具体单项提交,由后端统一校验并补齐继承值。 events = events.filter((v) => v.projectType == 1 && v.id != "allItems")
events = events.filter((v) => v.projectType == 1)
} else { } else {
this.$set(this.eventForm, "activityId", this.formData.id) this.$set(this.eventForm, "activityId", this.formData.id)
this.$set(this.eventForm, "eventId", this.eventForm.checkedEventId) this.$set(this.eventForm, "eventId", this.eventForm.checkedEventId)
@@ -1209,10 +1192,9 @@
this.$set(this.eventForm, "startAgeDate", this.eventForm.ageDate[0]) this.$set(this.eventForm, "startAgeDate", this.eventForm.ageDate[0])
this.$set(this.eventForm, "endAgeDate", this.eventForm.ageDate[1]) this.$set(this.eventForm, "endAgeDate", this.eventForm.ageDate[1])
} }
const allItems = this.checkedEventInfo.find((v) => v.eventId === "allItems") events = this.eventForm
events = allItems ? [clone(allItems), this.eventForm] : [this.eventForm]
if (this.copyTemplateMode) { if (this.copyTemplateMode) {
this.cleanCopyTemplateData(null, events) this.cleanCopyTemplateData(null, [events])
} }
} }
const loading = this.$loading({ const loading = this.$loading({
@@ -1233,7 +1215,6 @@
this.copyTemplateMode = false this.copyTemplateMode = false
} }
await this.openEdit(formData, false) await this.openEdit(formData, false)
this.currentEventId = this.eventForm.checkedEventId || ""
this.$message.success(resp.msg) this.$message.success(resp.msg)
} else { } else {
this.$message.error(resp.msg) this.$message.error(resp.msg)
@@ -1,11 +1,4 @@
const REGISTER_INFO_COMPONENT = { const REGISTER_INFO_COMPONENT = {
// 默认展示换届报告,仅由不需要查看该字段的业务页面显式关闭。
props: {
hideReplaceReport: {
type: Boolean,
default: false
}
},
template: ` template: `
<div> <div>
<el-tabs tab-position="top" v-model="activeName"> <el-tabs tab-position="top" v-model="activeName">
@@ -43,7 +36,7 @@ const REGISTER_INFO_COMPONENT = {
{{viewData.createTime}} {{viewData.createTime}}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="会费标准" :span="4"> <el-descriptions-item label="会费标准" :span="4">
{{ viewData.due === null || viewData.due === undefined ? "--" : viewData.due + " 元/年" }} {{viewData.due}}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item :span="4"> <el-descriptions-item :span="4">
<template slot="label">社团介绍</template> <template slot="label">社团介绍</template>
@@ -73,7 +66,7 @@ const REGISTER_INFO_COMPONENT = {
<template slot="label">其他附件</template> <template slot="label">其他附件</template>
<file-preview :files="viewData.files" complete_result></file-preview> <file-preview :files="viewData.files" complete_result></file-preview>
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item :span="2" v-if="!hideReplaceReport"> <el-descriptions-item :span="2">
<template slot="label">换届报告</template> <template slot="label">换届报告</template>
<file-preview :files="viewData.replaceReport" complete_result></file-preview> <file-preview :files="viewData.replaceReport" complete_result></file-preview>
</el-descriptions-item> </el-descriptions-item>
@@ -68,12 +68,10 @@ layout("/layouts/platform.html"){
</el-steps> </el-steps>
<!-- 第一步:基础信息表单 --> <!-- 第一步:基础信息表单 -->
<!-- 本表单自行提示首个必填错误,关闭全局校验弹窗增强以免重复提示。 -->
<el-form <el-form
v-show="step === 0" v-show="step === 0"
:model="formData" :model="formData"
ref="baseForm" ref="baseForm"
no-auto-enhance
:rules="baseRules" :rules="baseRules"
label-width="150px" label-width="150px"
class="main-form" class="main-form"
@@ -137,9 +135,7 @@ layout("/layouts/platform.html"){
v-model="formData.due" v-model="formData.due"
placeholder="请输入会费标准" placeholder="请输入会费标准"
readonly readonly
> ></el-input>
<template slot="append">元/年</template>
</el-input>
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row>
@@ -150,7 +146,7 @@ layout("/layouts/platform.html"){
<el-form-item <el-form-item
class="is-required" class="is-required"
label="本年度活动开展情况" label="本年度活动开展情况"
prop="yearActivityList" prop="yearActivity"
> >
<span class="tip-text">说明:非赛事活动无需填写赛事举办单位、获奖情况。</span> <span class="tip-text">说明:非赛事活动无需填写赛事举办单位、获奖情况。</span>
<el-table <el-table
@@ -642,7 +638,6 @@ layout("/layouts/platform.html"){
id: GetQueryString("bizId"), id: GetQueryString("bizId"),
taskId: GetQueryString("taskId"), taskId: GetQueryString("taskId"),
step: 0, // 当前步骤(0-基础信息,1-财务收支,2-成员名单) step: 0, // 当前步骤(0-基础信息,1-财务收支,2-成员名单)
formLoading: false,
isRegistered: false, // 是否已登记 isRegistered: false, // 是否已登记
currentClubId: "", // 当前选中社团ID currentClubId: "", // 当前选中社团ID
@@ -661,7 +656,7 @@ layout("/layouts/platform.html"){
year: new Date().getFullYear() + "", // 默认当前年度 year: new Date().getFullYear() + "", // 默认当前年度
clubId: "", clubId: "",
foundTime: "", foundTime: "",
due: null, due: "",
clubName: "", clubName: "",
yearActivityList: [{}], // 本年度活动列表 yearActivityList: [{}], // 本年度活动列表
plans: [{}], // 下年度计划列表 plans: [{}], // 下年度计划列表
@@ -676,10 +671,7 @@ layout("/layouts/platform.html"){
}, },
// 表单校验规则 // 表单校验规则
baseRules: { baseRules: {},
yearActivityList: [{validator: this.validateActivityRows, trigger: ['change', 'blur']}],
plans: [{validator: this.validateActivityRows, trigger: ['change', 'blur']}]
},
financeRules: {}, financeRules: {},
memberRules: {} memberRules: {}
}; };
@@ -732,28 +724,6 @@ layout("/layouts/platform.html"){
} }
}, },
methods: { 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) // 格式化数字(保留2位小数,空值显示0)
formatNumber(num) { formatNumber(num) {
@@ -1140,7 +1110,7 @@ layout("/layouts/platform.html"){
if (!val) return; if (!val) return;
const club = this.clubOptions.find(item => item.id === val); const club = this.clubOptions.find(item => item.id === val);
// 更新社团基础信息 // 更新社团基础信息
this.formData.due = club && club.due !== null && club.due !== undefined ? club.due : null; this.formData.due = club?.due || "";
this.formData.foundTime = club?.foundTime this.formData.foundTime = club?.foundTime
? this.$moment(club.foundTime).format("YYYY-MM-DD") ? this.$moment(club.foundTime).format("YYYY-MM-DD")
: this.$moment().format("YYYY-MM-DD"); : this.$moment().format("YYYY-MM-DD");
@@ -1191,63 +1161,73 @@ layout("/layouts/platform.html"){
// 步骤前进 // 步骤前进
this.step++; this.step++;
}, },
// 三个操作共用基础信息校验,失败时返回第一步展示行号和必填字段。 // 保存
validateBaseBeforeAction(action) { async handleSave() {
if (this.formLoading) return; try {
this.$refs.baseForm.validate((valid, fields) => { this.recalculateCensus();
if (!valid) { const resp = await this.$axios.post("/platform/club/examine/apply/save", {
this.step = 0; data: JSON.stringify(this.formData),
const firstError = Object.values(fields || {}).flat()[0]; incomeDetailed: JSON.stringify(this.financeData.incomeDetailed),
this.$message.warning('温馨提示:' + (firstError ? firstError.message : '请补全基础信息必填项')); incomeCensus: JSON.stringify(this.financeData.incomeCensus)
return;
}
action();
});
},
// 仅接收本页三个固定操作的接口后缀;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;
}); });
if (resp.code === 0) {
this.$message.success(resp.msg);
commonUtil.pjaxPush('/platform/club/examine/mine');
}
} catch (error) {
this.handleError(error, "保存失败");
}
}, },
handleSave() { // 提交(新增)
this.validateBaseBeforeAction(() => this.requestExamineAction('save')); async handleSubmit() {
try {
this.recalculateCensus();
await this.$confirm("确定要提交此申请吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "info"
});
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, "提交失败");
}
}
}, },
handleSubmit() { // 提交(任务处理)
this.validateBaseBeforeAction(() => { async handleFinishTask() {
this.$confirm('确定要提交此申请吗?', '提示', { try {
confirmButtonText: '确定', cancelButtonText: '取消', type: 'info' this.recalculateCensus();
}).then(() => this.requestExamineAction('submit')).catch(() => {}); await this.$confirm("您确定要提交吗?", "提示", {
}); confirmButtonText: "确定",
}, cancelButtonText: "取消",
handleFinishTask() { type: "warning"
this.validateBaseBeforeAction(() => { });
this.$confirm('您确定要提交吗?', '提示', { const resp = await this.$axios.post('/platform/club/examine/apply/submitAgain', {
confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' data: JSON.stringify(this.formData),
}).then(() => this.requestExamineAction('submitAgain')).catch(() => {}); 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, "提交失败");
}
}
} }
} }
}); });
@@ -10,7 +10,7 @@
<el-descriptions v-if="viewData" :column="3" border class="table_fixed"> <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.clubName }}</el-descriptions-item>
<el-descriptions-item label="成立时间">{{ viewData.foundTime }} </el-descriptions-item> <el-descriptions-item label="成立时间">{{ viewData.foundTime }} </el-descriptions-item>
<el-descriptions-item label="会费标准">{{ viewData.due === null || viewData.due === undefined ? "--" : viewData.due + " 元/年" }}</el-descriptions-item> <el-descriptions-item label="会费标准">{{ viewData.due }}</el-descriptions-item>
<el-descriptions-item span="3"> <el-descriptions-item span="3">
<template slot="label">工作总结</template> <template slot="label">工作总结</template>
<file-preview :files="viewData.summaryFiles" complete_result></file-preview> <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 label="联系人邮箱">{{viewData.concatPersonEmail}}</el-descriptions-item>
<el-descriptions-item> <el-descriptions-item>
<template slot="label">会费标准</template> <template slot="label">会费标准</template>
{{ viewData.due === null || viewData.due === undefined ? "--" : viewData.due + " 元/年" }} {{viewData.due}}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item :span="2"> <el-descriptions-item :span="2">
<template slot="label">当前人数</template> <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.clubCode}}</el-descriptions-item>
<el-descriptions-item label="社团类型">{{viewData.typeName}}</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.sponsorName}}</el-descriptions-item>
<el-descriptions-item label="会费标准">{{ viewData.due === null || viewData.due === undefined ? "--" : viewData.due + " 元/年" }}</el-descriptions-item> <el-descriptions-item label="会费标准">{{viewData.due}}</el-descriptions-item>
<el-descriptions-item label="当前人数">{{viewData.currentPeopleNum}}</el-descriptions-item> <el-descriptions-item label="当前人数">{{viewData.currentPeopleNum}}</el-descriptions-item>
<el-descriptions-item span="2"> <el-descriptions-item span="2">
<template slot="label">社团介绍</template> <template slot="label">社团介绍</template>
@@ -24,13 +24,7 @@ const CLUB_FORM_TEMPLATE = {
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<el-form-item label="会费标准" prop="due"> <el-form-item label="会费标准" prop="due">
<el-input <el-input placeholder="请填写会费标准" v-model="formData.due">
min="0"
placeholder="请填写会费标准"
step="0.01"
type="number"
v-model.number="formData.due"
>
<template slot="append">元/年</template> <template slot="append">元/年</template>
</el-input> </el-input>
</el-form-item> </el-form-item>
@@ -162,15 +156,12 @@ const CLUB_FORM_TEMPLATE = {
return { return {
formData: { formData: {
clubName: "", clubName: "",
clubType: "", clubType: ""
due: null
}, },
formRules: { formRules: {
clubName: [{ required: true, message: "请填写社团名称", trigger: ["blur", "change"] }], clubName: [{ required: true, message: "请填写社团名称", trigger: ["blur", "change"] }],
clubCode: [{ required: false, message: "请填写社团编码", trigger: ["blur", "change"] }], clubCode: [{ required: false, message: "请填写社团编码", trigger: ["blur", "change"] }],
clubType: [{ required: true, 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"] }], //concatPerson: [{ required: true, message: "请选择社团联系人", trigger: ["blur", "change"] }],
foundTime: [{ required: true, message: "请选择成立时间", trigger: ["blur", "change"] }] foundTime: [{ required: true, message: "请选择成立时间", trigger: ["blur", "change"] }]
} }
@@ -73,7 +73,7 @@ layout("/layouts/platform.html"){
</el-card> </el-card>
<template #public> <template #public>
<register-info ref="registerInfoRef" :hide-replace-report="true"> <register-info ref="registerInfoRef">
<div v-if="showApprovalForm"> <div v-if="showApprovalForm">
<div class="process-title"> <div class="process-title">
{{formData.taskName}} {{formData.taskName}}
@@ -21,14 +21,29 @@ const PROPOSAL_INFO = {
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="提案类别"> <el-descriptions-item label="提案类别">
{{viewData.typeName}} {{viewData.typeName}}
</el-descriptions-item><!-- </el-descriptions-item>
<!-- 提案方式和联系电话来自提案详情,未填写时显示占位符。 -->
<el-descriptions-item label="提案方式">
<dict-tag v-if="viewData.source" :options="dict.type.PROPOSAL_SOURCE"
:value="viewData.source"></dict-tag>
<span v-else>--</span>
</el-descriptions-item>
<el-descriptions-item label="联系电话">{{ viewData.mobile || '--' }}</el-descriptions-item><!--
<el-descriptions-item label="建议承办单位"> <el-descriptions-item label="建议承办单位">
{{viewData.suggestUnits}} {{viewData.suggestUnits}}
</el-descriptions-item>--> </el-descriptions-item>-->
<el-descriptions-item label="立案结果" :span="2"> <!-- 立案结果和类型尚未填写时仍显示字段,并以双横线表示空值。 -->
<dict-tag :options="dict.type.PROPOSAL_CASE_FILING_RESULT" <el-descriptions-item label="立案结果">
<dict-tag v-if="viewData.caseFilingResult" :options="dict.type.PROPOSAL_CASE_FILING_RESULT"
:value="viewData.caseFilingResult"></dict-tag> :value="viewData.caseFilingResult"></dict-tag>
<span v-else>--</span>
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="立案类型">
<dict-tag v-if="viewData.caseFilingType" :options="dict.type.PROPOSAL_CASE_FILING_TYPE"
:value="viewData.caseFilingType"></dict-tag>
<span v-else>--</span>
</el-descriptions-item>
<el-descriptions-item></el-descriptions-item>
<!--<el-descriptions-item label="调研情况" :span="3"> <!--<el-descriptions-item label="调研情况" :span="3">
<div class="text-left">{{viewData.researchFindings}}</div> <div class="text-left">{{viewData.researchFindings}}</div>
</el-descriptions-item>--> </el-descriptions-item>-->
@@ -82,18 +97,22 @@ const PROPOSAL_INFO = {
{{row.commissionerName}}({{row.commissionerLoginName}}) {{row.commissionerName}}({{row.commissionerLoginName}})
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="立案结果" prop="caseFilingType"> <!-- 委员意见分别展示立案结果和类型,未填写时显示双横线。 -->
<el-table-column label="立案结果" prop="caseFilingResult">
<template slot-scope="{row}"> <template slot-scope="{row}">
<div style="display: flex;justify-content: center"> <div style="display: flex;justify-content: center">
<dict-tag :options="dict.type.PROPOSAL_CASE_FILING_RESULT" <dict-tag v-if="row.caseFilingResult" :options="dict.type.PROPOSAL_CASE_FILING_RESULT"
:value="row.caseFilingResult"></dict-tag> :value="row.caseFilingResult"></dict-tag>
<!-- 普通提案/重点提案暂不展示,历史数据仍保留。 --> <span v-else>--</span>
<template v-if="false"> </div>
( </template>
<dict-tag :options="dict.type.PROPOSAL_CASE_FILING_TYPE" </el-table-column>
:value="row.caseFilingType"></dict-tag> <el-table-column label="立案类型" prop="caseFilingType">
) <template slot-scope="{row}">
</template> <div style="display: flex;justify-content: center">
<dict-tag v-if="row.caseFilingType" :options="dict.type.PROPOSAL_CASE_FILING_TYPE"
:value="row.caseFilingType"></dict-tag>
<span v-else>--</span>
</div> </div>
</template> </template>
</el-table-column> </el-table-column>
@@ -124,117 +143,125 @@ const PROPOSAL_INFO = {
</span> </span>
</div> </div>
</template> </template>
<!--同一提案可能存在多个承办单位答复任务,查看时合并为一个流程节点,节点内保留每条答复。--> <!--同一提案可能存在多个承办单位答复任务,查看时合并为一个流程节点,节点内保留每条答复。-->
<template v-if="task.replyTasks"> <template v-if="task.replyTasks">
<el-descriptions v-for="replyTask in task.replyTasks" border class="flow-task-form" <el-descriptions v-for="replyTask in task.replyTasks" border class="flow-task-form"
:column="3" :key="replyTask.id"> :column="3" :key="replyTask.id">
<el-descriptions-item label="承办单位" :span="3"> <el-descriptions-item label="承办单位" :span="3">
{{replyTask.ext.underTakeName}} {{replyTask.ext.underTakeName}}
</el-descriptions-item>
<el-descriptions-item label="办理用户">
{{replyTask.taskFormData.userName}}({{replyTask.taskFormData.loginName}})
</el-descriptions-item>
<el-descriptions-item label="办理时间">{{replyTask.finishTime}}</el-descriptions-item>
<el-descriptions-item label="办理结果">
<dict-tag :options="dict.type.PROCESS_TASK_SUBMIT_TYPE"
:value="replyTask.ext.submitType"></dict-tag>
</el-descriptions-item>
<el-descriptions-item label="办理意见" :span="3">
<div v-html="replyTask.taskFormData.tf_opinion"></div>
</el-descriptions-item>
</el-descriptions>
</template>
<el-descriptions border class="flow-task-form" :column="3" :key="task.id"
v-else-if="task.ext.isFirstTaskNode">
<el-descriptions-item label="申请用户">{{ task.ext.initiatorName
}}({{task.ext.initiatorAccount}})
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="办理用户"> <el-descriptions-item label="申请时间">{{ task.finishTime }}</el-descriptions-item>
{{replyTask.taskFormData.userName}}({{replyTask.taskFormData.loginName}})
</el-descriptions-item>
<el-descriptions-item label="办理时间">{{replyTask.finishTime}}</el-descriptions-item>
<el-descriptions-item label="办理结果"> <el-descriptions-item label="办理结果">
<dict-tag :options="dict.type.PROCESS_TASK_SUBMIT_TYPE" <dict-tag :options="dict.type.PROCESS_TASK_SUBMIT_TYPE"
:value="replyTask.ext.submitType"></dict-tag> :value="task.ext.submitType"></dict-tag>
</el-descriptions-item>
<el-descriptions-item label="办理意见" :span="3">
<div v-html="replyTask.taskFormData.tf_opinion"></div>
</el-descriptions-item> </el-descriptions-item>
</el-descriptions> </el-descriptions>
</template>
<el-descriptions border class="flow-task-form" :column="3" :key="task.id"
v-else-if="task.ext.isFirstTaskNode">
<el-descriptions-item label="申请用户">{{ task.ext.initiatorName
}}({{task.ext.initiatorAccount}})
</el-descriptions-item>
<el-descriptions-item label="申请时间">{{ task.finishTime }}</el-descriptions-item>
<el-descriptions-item label="办理结果">
<dict-tag :options="dict.type.PROCESS_TASK_SUBMIT_TYPE"
:value="task.ext.submitType"></dict-tag>
</el-descriptions-item>
</el-descriptions>
<!--邀请附议人--> <!--邀请附议人-->
<el-descriptions border class="flow-task-form" :column="3" :key="task.id" <el-descriptions border class="flow-task-form" :column="3" :key="task.id"
v-else-if="task.taskName === 'invite'"> v-else-if="task.taskName === 'invite'">
<el-descriptions-item label="办理用户">{{ task.taskFormData.userName <el-descriptions-item label="办理用户">{{ task.taskFormData.userName
}}({{task.taskFormData.loginName}}) }}({{task.taskFormData.loginName}})
</el-descriptions-item>
<el-descriptions-item label="办理时间" :span="2">{{ task.finishTime }}</el-descriptions-item>
<el-descriptions-item label="附议人" :span="3" v-if="task.taskName === 'invite'">
<el-table v-loading="tableLoading" :data="task?.taskFormData?.seconder">
<el-table-column label="姓名" prop="userName"></el-table-column>
<el-table-column label="工号" prop="loginName"></el-table-column>
<el-table-column label="性别" prop="sex"></el-table-column>
<el-table-column label="联系方式" prop="mobile"></el-table-column>
<el-table-column label="单位" prop="unitName"></el-table-column>
<el-table-column label="分工会" prop="unionName"></el-table-column>
<el-table-column label="代表团" prop="delegationName"></el-table-column>
</el-table>
</el-descriptions-item>
</el-descriptions>
<!--提案委员会立案-->
<el-descriptions border class="flow-task-form" :column="3" :key="task.id"
v-else-if="task.taskName === 'committee'||task.taskName === 'committeeFilingUnit'">
<el-descriptions-item label="办理用户">{{ task.taskFormData.userName
}}({{task.taskFormData.loginName}})
</el-descriptions-item>
<el-descriptions-item label="办理时间">{{ task.finishTime }}</el-descriptions-item>
<el-descriptions-item label="立案结果">
<div style="display: flex;justify-content: center">
<dict-tag :options="dict.type.PROPOSAL_CASE_FILING_RESULT"
:value="task.ext.tf_caseFilingResult"></dict-tag>
<!-- 普通提案/重点提案暂不展示,历史流程变量仍保留。 -->
<template v-if="false">
(
<dict-tag :options="dict.type.PROPOSAL_CASE_FILING_TYPE"
:value="task.ext.tf_caseFilingType"></dict-tag>
)
</template>
</div>
</el-descriptions-item>
<template v-if="['CONFIRM_FILING','SUGGESTION'].includes(task.ext.tf_caseFilingResult)">
<el-descriptions-item v-if="task.ext.tf_caseFilingResult === 'SUGGESTION'" label="承办单位" :span="3">{{task?.ext?.tf_masterUnitNameStr || task?.ext?.tf_slaveUnitNameStr}}
</el-descriptions-item> </el-descriptions-item>
<template v-else> <el-descriptions-item label="办理时间" :span="2">{{ task.finishTime }}
<el-descriptions-item label="主办单位" :span="3">{{task?.ext?.tf_masterUnitNameStr}} </el-descriptions-item>
</el-descriptions-item> <el-descriptions-item label="附议人" :span="3" v-if="task.taskName === 'invite'">
<el-descriptions-item label="协办单位" :span="3">{{task?.ext?.tf_slaveUnitNameStr}} <el-table v-loading="tableLoading" :data="task?.taskFormData?.seconder">
<el-table-column label="姓名" prop="userName"></el-table-column>
<el-table-column label="工号" prop="loginName"></el-table-column>
<el-table-column label="性别" prop="sex"></el-table-column>
<el-table-column label="联系方式" prop="mobile"></el-table-column>
<el-table-column label="单位" prop="unitName"></el-table-column>
<el-table-column label="分工会" prop="unionName"></el-table-column>
<el-table-column label="代表团" prop="delegationName"></el-table-column>
</el-table>
</el-descriptions-item>
</el-descriptions>
<!--提案委员会立案-->
<el-descriptions border class="flow-task-form" :column="3" :key="task.id"
v-else-if="task.taskName === 'committee'||task.taskName === 'committeeFilingUnit'">
<el-descriptions-item label="办理用户">{{ task.taskFormData.userName
}}({{task.taskFormData.loginName}})
</el-descriptions-item>
<el-descriptions-item label="办理时间">{{ task.finishTime }}</el-descriptions-item>
<!-- 立案节点分别展示流程记录中的结果与类型,空值显示双横线。 -->
<el-descriptions-item label="立案结果">
<div style="display: flex;justify-content: center">
<dict-tag v-if="task.ext.tf_caseFilingResult"
:options="dict.type.PROPOSAL_CASE_FILING_RESULT"
:value="task.ext.tf_caseFilingResult"></dict-tag>
<span v-else>--</span>
</div>
</el-descriptions-item>
<el-descriptions-item label="立案类型">
<div style="display: flex;justify-content: center">
<dict-tag v-if="task.ext.tf_caseFilingType"
:options="dict.type.PROPOSAL_CASE_FILING_TYPE"
:value="task.ext.tf_caseFilingType"></dict-tag>
<span v-else>--</span>
</div>
</el-descriptions-item>
<template v-if="['CONFIRM_FILING','SUGGESTION'].includes(task.ext.tf_caseFilingResult)">
<el-descriptions-item v-if="task.ext.tf_caseFilingResult === 'SUGGESTION'"
label="承办单位" :span="3">{{task?.ext?.tf_masterUnitNameStr ||
task?.ext?.tf_slaveUnitNameStr}}
</el-descriptions-item> </el-descriptions-item>
<template v-else>
<el-descriptions-item label="主办单位" :span="3">{{task?.ext?.tf_masterUnitNameStr}}
</el-descriptions-item>
<el-descriptions-item label="协办单位" :span="3">{{task?.ext?.tf_slaveUnitNameStr}}
</el-descriptions-item>
</template>
</template> </template>
</template> <el-descriptions-item label="办理意见" :span="3">
<el-descriptions-item label="办理意见" :span="3"> <div v-html="task.taskFormData.tf_opinion"></div>
<div v-html="task.taskFormData.tf_opinion"></div> </el-descriptions-item>
</el-descriptions-item> </el-descriptions>
</el-descriptions>
<el-descriptions border class="flow-task-form" :column="3" :key="task.id" v-else> <el-descriptions border class="flow-task-form" :column="3" :key="task.id" v-else>
<el-descriptions-item label="承办单位" <el-descriptions-item label="承办单位"
v-if="['opinion_unit_reply','unit_reply','two_unit_reply','opinion_master_reply','master_reply','slave_reply'].includes(task.taskName)" :span="3"> v-if="['opinion_unit_reply','unit_reply','two_unit_reply','opinion_master_reply','master_reply','slave_reply'].includes(task.taskName)"
{{task.ext.underTakeName}} :span="3">
</el-descriptions-item> {{task.ext.underTakeName}}
</el-descriptions-item>
<el-descriptions-item label="办理用户">{{ task.taskFormData.userName <el-descriptions-item label="办理用户">{{ task.taskFormData.userName
}}({{task.taskFormData.loginName}}) }}({{task.taskFormData.loginName}})
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="办理时间">{{ task.finishTime }}</el-descriptions-item> <el-descriptions-item label="办理时间">{{ task.finishTime }}</el-descriptions-item>
<el-descriptions-item label="办理评价" v-if="['feedback'].includes(task.taskName)" :span="3"> <el-descriptions-item label="办理评价" v-if="['feedback'].includes(task.taskName)"
<dict-tag :options="dict.type.PROPOSAL_FEEDBACK" :span="3">
:value="task.ext.tf_feedback"></dict-tag> <dict-tag :options="dict.type.PROPOSAL_FEEDBACK"
</el-descriptions-item> :value="task.ext.tf_feedback"></dict-tag>
<el-descriptions-item label="办理结果" v-else> </el-descriptions-item>
<dict-tag :options="dict.type.PROCESS_TASK_SUBMIT_TYPE" <el-descriptions-item label="办理结果" v-else>
:value="task.ext.submitType"></dict-tag> <dict-tag :options="dict.type.PROCESS_TASK_SUBMIT_TYPE"
</el-descriptions-item> :value="task.ext.submitType"></dict-tag>
<el-descriptions-item label="办理意见" :span="3" v-if="!task.ext.isFirstTaskNode"> </el-descriptions-item>
<div v-html="task.taskFormData.tf_opinion"></div> <el-descriptions-item label="办理意见" :span="3" v-if="!task.ext.isFirstTaskNode">
</el-descriptions-item> <div v-html="task.taskFormData.tf_opinion"></div>
</el-descriptions> </el-descriptions-item>
</el-descriptions>
</el-collapse-item> </el-collapse-item>
</el-collapse> </el-collapse>
</template> </template>
@@ -243,7 +270,7 @@ const PROPOSAL_INFO = {
<snaker-chart ref="snakerChartRef"></snaker-chart> <snaker-chart ref="snakerChartRef"></snaker-chart>
</div> </div>
`, `,
dicts: ["PROPOSAL_TYPE", "PROPOSAL_CASE_FILING_RESULT", "PROPOSAL_CASE_FILING_TYPE", "PROCESS_TASK_SUBMIT_TYPE", "PROPOSAL_FEEDBACK"], dicts: ["PROPOSAL_TYPE", "PROPOSAL_SOURCE", "PROPOSAL_CASE_FILING_RESULT", "PROPOSAL_CASE_FILING_TYPE", "PROCESS_TASK_SUBMIT_TYPE", "PROPOSAL_FEEDBACK"],
props: { props: {
hide_slave: false hide_slave: false
}, },
@@ -60,7 +60,10 @@ const USER_COMPONENT = {
}, },
filterUnit(query, item) { filterUnit(query, item) {
return item.username.indexOf(query) > -1 // 按姓名或工号筛选人员,字段为空时按空字符串处理。
const username = item.username || ""
const loginname = item.loginname || ""
return username.indexOf(query) > -1 || loginname.indexOf(query) > -1
} }
} }
} }
@@ -23,9 +23,9 @@ layout("/layouts/platform.html"){
</el-card> </el-card>
<el-card shadow="never"> <el-card shadow="never">
<table-tool :columns.sync="tableColumns"> <table-tool :columns.sync="tableColumns">
<el-button class="ml5" icon="el-icon-upload2" type="primary" size="small" <!-- <el-button class="ml5" icon="el-icon-upload2" type="primary" size="small"
@click="openImport">导入提案 @click="openImport">导入提案
</el-button> </el-button>-->
</table-tool> </table-tool>
<el-table v-loading="tableLoading" :data="tableData" @sort-change="pageOrder" header-align="center" style="width: 100%" row-key="id"> <el-table v-loading="tableLoading" :data="tableData" @sort-change="pageOrder" header-align="center" style="width: 100%" row-key="id">
<el-table-column label="序号" width="50" type="index" :index="indexMethod" fixed="left"></el-table-column> <el-table-column label="序号" width="50" type="index" :index="indexMethod" fixed="left"></el-table-column>
@@ -76,8 +76,7 @@ layout("/layouts/platform.html"){
<el-button v-if="row.canRevoke" size="mini" type="danger" @click="openRevoke(row)"> <el-button v-if="row.canRevoke" size="mini" type="danger" @click="openRevoke(row)">
撤回 撤回
</el-button> </el-button>
<!-- v-if="row.taskKey === 'startTask' || !row.instanceId"--> <el-button v-if="row.taskKey === 'startTask' || !row.instanceId" size="mini" type="danger" @click="del(row.id)">
<el-button size="mini" type="danger" @click="del(row.id)">
删除 删除
</el-button> </el-button>
</template> </template>
@@ -8,7 +8,7 @@ layout("/layouts/platform.html"){
<custom-card> <custom-card>
<snaker-start slot="header" label="撰写提案" define_key="JDHTA_NC"> <snaker-start slot="header" label="撰写提案" define_key="JDHTA_NC">
<template slot="header-right-label"> <template slot="header-right-label">
<el-link type="primary" @click="openImport" v-if="!formData.id" style="margin-right: 15px">导入提案</el-link> <!-- <el-link type="primary" @click="openImport" v-if="!formData.id" style="margin-right: 15px">导入提案</el-link>-->
</template> </template>
</snaker-start> </snaker-start>
<el-form :model="formData" :rules="formRules" label-width="120px" ref="addForm"> <el-form :model="formData" :rules="formRules" label-width="120px" ref="addForm">
@@ -77,7 +77,7 @@ layout("/layouts/platform_h5.html"){
<van-cell title="会长">{{ clubDetail.clubLeader || '--' }}</van-cell> <van-cell title="会长">{{ clubDetail.clubLeader || '--' }}</van-cell>
<van-cell title="秘书长">{{ clubDetail.clubSecretary || '--' }}</van-cell> <van-cell title="秘书长">{{ clubDetail.clubSecretary || '--' }}</van-cell>
<van-cell title="当前人数">{{ clubDetail.currentPeopleNum || 0 }} 人</van-cell> <van-cell title="当前人数">{{ clubDetail.currentPeopleNum || 0 }} 人</van-cell>
<van-cell title="会费标准">{{ clubDetail.due === null || clubDetail.due === undefined ? '--' : clubDetail.due + ' 元/年' }}</van-cell> <van-cell title="会费标准">{{ 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.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"><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> <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>
@@ -9,26 +9,30 @@ const PROPOSAL_INFO = {
<van-cell-group :border="false"> <van-cell-group :border="false">
<van-cell title="提案编号">{{ viewData.code || '--' }}</van-cell> <van-cell title="提案编号">{{ viewData.code || '--' }}</van-cell>
<van-cell title="提案名称">{{ viewData.name || '--' }}</van-cell> <van-cell title="提案名称">{{ viewData.name || '--' }}</van-cell>
<van-cell title="立案结果" v-if="viewData.caseFilingResult">
<dict-tag :options="dict.type.PROPOSAL_CASE_FILING_RESULT" <van-cell title="提案类别">{{ viewData.typeName || '--' }}</van-cell>
:value="viewData.caseFilingResult"></dict-tag>
</van-cell>
<van-cell title="立案类型" v-if="viewData.caseFilingType">
<dict-tag :options="dict.type.PROPOSAL_CASE_FILING_TYPE"
:value="viewData.caseFilingType"></dict-tag>
</van-cell>
<van-cell title="提案类型">{{ viewData.typeName || '--' }}</van-cell>
<van-cell title="提案方式">{{ getDictText(dict.type.PROPOSAL_SOURCE, viewData.source) }} <van-cell title="提案方式">{{ getDictText(dict.type.PROPOSAL_SOURCE, viewData.source) }}
</van-cell> </van-cell>
<van-cell title="代表姓名">{{ viewData.createUserName || '--' }}</van-cell> <van-cell title="提案人">{{ viewData.createUserName || '--' }}</van-cell>
<van-cell title="所属教代会">{{ viewData.sessionName || viewData.fullName || '--' }}</van-cell> <van-cell title="教代会届次">{{ viewData.sessionName || viewData.fullName || '--' }}</van-cell>
<van-cell :title="viewData.committeeName ? '所属委员会' : '所属代表团'"> <van-cell :title="viewData.committeeName ? '委员会名称' : '代表团名称'">
{{ viewData.committeeName || viewData.delegationName || '--' }} {{ viewData.committeeName || viewData.delegationName || '--' }}
</van-cell> </van-cell>
<van-cell title="单位">{{ viewData.unitName || '--' }}</van-cell> <van-cell title="提案人单位">{{ viewData.unitName || '--' }}</van-cell>
<van-cell title="联系电话">{{ viewData.mobile || '--' }}</van-cell> <van-cell title="联系电话">{{ viewData.mobile || '--' }}</van-cell>
<van-cell title="提案时间">{{ viewData.createTime || '--' }}</van-cell> <van-cell title="提案时间">{{ viewData.createTime || '--' }}</van-cell>
<van-cell title="案由" class="proposal-view-long-cell"> <!-- 立案结果和类型尚未填写时仍显示字段,并以双横线表示空值。 -->
<van-cell title="立案结果">
<dict-tag v-if="viewData.caseFilingResult" :options="dict.type.PROPOSAL_CASE_FILING_RESULT"
:value="viewData.caseFilingResult"></dict-tag>
<span v-else>--</span>
</van-cell>
<van-cell title="立案类型">
<dict-tag v-if="viewData.caseFilingType" :options="dict.type.PROPOSAL_CASE_FILING_TYPE"
:value="viewData.caseFilingType"></dict-tag>
<span v-else>--</span>
</van-cell>
<van-cell title="提案案由" class="proposal-view-long-cell">
<div class="proposal-view-rich-value" v-html="viewData.brief || '--'"></div> <div class="proposal-view-rich-value" v-html="viewData.brief || '--'"></div>
</van-cell> </van-cell>
<van-cell title="建议措施" class="proposal-view-long-cell"> <van-cell title="建议措施" class="proposal-view-long-cell">
@@ -48,7 +52,7 @@ const PROPOSAL_INFO = {
:key="merge.id" :key="merge.id"
class="proposal-view-merge-cell" class="proposal-view-merge-cell"
:title="(index + 1) + '. ' + merge.name" :title="(index + 1) + '. ' + merge.name"
:label="'提案编号:' + merge.code + '|提案人:' + merge.createUserName + '|类别:' + merge.typeName + '|代表团:' + merge.delegationName" :label="'提案编号:' + merge.code + '|提案人:' + merge.createUserName + '|提案类别:' + merge.typeName + '|代表团:' + merge.delegationName"
is-link is-link
@click="openView(merge)"> @click="openView(merge)">
</van-cell> </van-cell>
@@ -66,13 +70,16 @@ const PROPOSAL_INFO = {
<template v-if="opinion.commissionerLoginName">({{ opinion.commissionerLoginName }}) <template v-if="opinion.commissionerLoginName">({{ opinion.commissionerLoginName }})
</template> </template>
</van-cell> </van-cell>
<!-- 委员意见分别展示立案结果和类型,未填写时显示双横线。 -->
<van-cell title="立案结果"> <van-cell title="立案结果">
<dict-tag :options="dict.type.PROPOSAL_CASE_FILING_RESULT" <dict-tag v-if="opinion.caseFilingResult" :options="dict.type.PROPOSAL_CASE_FILING_RESULT"
:value="opinion.caseFilingResult"></dict-tag> :value="opinion.caseFilingResult"></dict-tag>
<span v-else>--</span>
</van-cell> </van-cell>
<van-cell title="立案类型" v-if="opinion.caseFilingType"> <van-cell title="立案类型">
<dict-tag :options="dict.type.PROPOSAL_CASE_FILING_TYPE" <dict-tag v-if="opinion.caseFilingType" :options="dict.type.PROPOSAL_CASE_FILING_TYPE"
:value="opinion.caseFilingType"></dict-tag> :value="opinion.caseFilingType"></dict-tag>
<span v-else>--</span>
</van-cell> </van-cell>
<van-cell title="意见" v-if="opinion.opinionText" class="proposal-view-long-cell"> <van-cell title="意见" v-if="opinion.opinionText" class="proposal-view-long-cell">
<div class="proposal-view-rich-value">{{ opinion.opinionText }}</div> <div class="proposal-view-rich-value">{{ opinion.opinionText }}</div>
@@ -158,13 +165,18 @@ const PROPOSAL_INFO = {
|| '--' }}) || '--' }})
</van-cell> </van-cell>
<van-cell title="办理时间">{{ selectedTask.finishTime || '--' }}</van-cell> <van-cell title="办理时间">{{ selectedTask.finishTime || '--' }}</van-cell>
<!-- 立案节点分别展示流程记录中的结果与类型,空值显示双横线。 -->
<van-cell title="立案结果"> <van-cell title="立案结果">
<dict-tag :options="dict.type.PROPOSAL_CASE_FILING_RESULT" <dict-tag v-if="selectedTask.ext.tf_caseFilingResult"
:options="dict.type.PROPOSAL_CASE_FILING_RESULT"
:value="selectedTask.ext.tf_caseFilingResult"></dict-tag> :value="selectedTask.ext.tf_caseFilingResult"></dict-tag>
<span v-else>--</span>
</van-cell> </van-cell>
<van-cell title="立案类型" v-if="selectedTask.ext.tf_caseFilingType"> <van-cell title="立案类型">
<dict-tag :options="dict.type.PROPOSAL_CASE_FILING_TYPE" <dict-tag v-if="selectedTask.ext.tf_caseFilingType"
:options="dict.type.PROPOSAL_CASE_FILING_TYPE"
:value="selectedTask.ext.tf_caseFilingType"></dict-tag> :value="selectedTask.ext.tf_caseFilingType"></dict-tag>
<span v-else>--</span>
</van-cell> </van-cell>
<template <template
v-if="['CONFIRM_FILING', 'SUGGESTION'].includes(selectedTask.ext.tf_caseFilingResult)"> v-if="['CONFIRM_FILING', 'SUGGESTION'].includes(selectedTask.ext.tf_caseFilingResult)">
@@ -187,7 +199,7 @@ const PROPOSAL_INFO = {
</template> </template>
<template v-else> <template v-else>
<van-cell title="办理单位" <van-cell title="承办单位"
v-if="['opinion_unit_reply', 'unit_reply', 'two_unit_reply', 'opinion_master_reply', 'master_reply', 'slave_reply'].includes(selectedTask.taskName)"> v-if="['opinion_unit_reply', 'unit_reply', 'two_unit_reply', 'opinion_master_reply', 'master_reply', 'slave_reply'].includes(selectedTask.taskName)">
{{ selectedTask.ext.underTakeName || '--' }} {{ selectedTask.ext.underTakeName || '--' }}
</van-cell> </van-cell>