Merge remote-tracking branch 'origin/main'

This commit is contained in:
2026-08-20 17:26:55 +08:00
29 changed files with 508 additions and 126 deletions
@@ -76,7 +76,8 @@ public class ClubExamineApplyController {
if(year == null) {
return Result.error("年份信息为空,请核查");
}
List<SysClubExamineRegister> list = dao.query(SysClubExamineRegister.class, Cnd.where("clubId", "=", clubId).and("year(registerDate)", "=", year).andEX("id", "!=", id));
// 同一社团的年度重复申请校验以登记年度为准,不能按申请时间判断。
List<SysClubExamineRegister> list = dao.query(SysClubExamineRegister.class, Cnd.where("clubId", "=", clubId).and("year", "=", year).andEX("id", "!=", id));
List<ProcessInstance> instanceList = dao.query(
ProcessInstance.class,
Cnd.where(ProcessInstance::getBusinessNo, "in", list.stream().map(SysClubExamineRegister::getId).toList())
@@ -146,8 +147,9 @@ public class ClubExamineApplyController {
if(StrUtil.isBlank(clubId)) {
return Result.error("社团信息为空,请核查");
}
// 上年度结余按登记年度查询,避免申请时间跨年导致取错数据。
SysClubExamineRegister register = dao.fetch(SysClubExamineRegister.class, Cnd.where("clubId", "=", clubId)
.and("YEAR(registerDate)", "=", DateUtil.thisYear() - 1));
.and("year", "=", DateUtil.thisYear() - 1));
if (Lang.isNotEmpty(register)) {
List<JSONObject> list = register.getIncomeCensus();
float surplus = list.get(0).getFloat("surplus");
@@ -87,7 +87,8 @@ public class ClubExamineMineController {
nutMap.setv("clubName", register.getClubName());
nutMap.setv("create_time", register.getFoundTime());
nutMap.setv("dues_standard", register.getDue());
nutMap.setv("year", register.getRegisterDate().substring(0, 4));
// 生成材料中的年度与列表展示的登记年度保持一致。
nutMap.setv("year", register.getYear());
nutMap.setv("registerDate", register.getRegisterDate());
nutMap.setv("incomeCensus", incomeCensus);
nutMap.setv("jgUser", jgUser);
@@ -24,6 +24,10 @@ public class ClubUserPageForm extends PageForm {
private Integer auditType;
private Integer source;
private Integer year;
/** 社团评优申报开始年度筛选条件。 */
private String applyStartYear;
/** 社团评优申报结束年度筛选条件。 */
private String applyEndYear;
private Boolean giveMoney;
private Integer radioType;
}
@@ -71,7 +71,9 @@ public class SysClubEvaluateServiceImpl extends BaseServiceImpl<SysClubEvaluate>
Cnd cnd = Cnd.NEW();
cnd.andEX("info.clubId", "=", pageForm.getClubId());
cnd.andEX("year(info.applyTime)", "=", pageForm.getYear());
// 评优年度以申报填写的起止年度为准,不能使用申请提交时间所在年份代替。
cnd.andEX("info.applyStartYear", "=", pageForm.getApplyStartYear());
cnd.andEX("info.applyEndYear", "=", pageForm.getApplyEndYear());
cnd.and("info.userId", "=", SecurityUtil.getUserId());
cnd.groupBy("info.id");
@@ -144,7 +146,9 @@ public class SysClubEvaluateServiceImpl extends BaseServiceImpl<SysClubEvaluate>
""");
cnd.andEX("ce.clubId", "=", pageForm.getClubId());
cnd.andEX("year(ce.applyTime)", "=", pageForm.getYear());
// 审核列表与参考项目保持一致,分别按申报开始年度和申报结束年度筛选。
cnd.andEX("ce.applyStartYear", "=", pageForm.getApplyStartYear());
cnd.andEX("ce.applyEndYear", "=", pageForm.getApplyEndYear());
if (pageForm.getAudit()) {
cnd.and("t.taskState", "in", List.of(ProcessTaskStateEnum.FINISHED.getCode(), ProcessTaskStateEnum.INTERRUPT.getCode()));
@@ -537,6 +537,7 @@ public class SysClubExamineServiceImpl extends BaseServiceImpl<SysClubExamineReg
*/
private void normalizeActivityData(ClubExamineVo examineVo) {
examineVo.setSummaryFiles(normalizeSummaryFiles(examineVo.getSummaryFiles()));
examineVo.setChangeUserNum(normalizeChangeUserNum(examineVo.getChangeUserNum()));
List<JSONObject> yearActivitySource = examineVo.getYearActivityList();
if (yearActivitySource == null || yearActivitySource.isEmpty()) {
yearActivitySource = examineVo.getYearActivitys();
@@ -553,6 +554,27 @@ public class SysClubExamineServiceImpl extends BaseServiceImpl<SysClubExamineReg
.collect(Collectors.toList()));
}
/**
* 将V3年审记录中的年度减少字段转换为查看页统一使用的V4字段。
* 历史年审必须展示申请时保存的数据,不能按当前会员状态重新统计。
*
* @param changeUserNum 年审记录保存的成员变化数据
* @return 兼容V3、V4字段的成员变化数据
*/
private List<JSONObject> normalizeChangeUserNum(List<JSONObject> changeUserNum) {
if (changeUserNum == null || changeUserNum.isEmpty()) {
return new ArrayList<>();
}
return changeUserNum.stream()
.filter(Objects::nonNull)
.peek(item -> {
if (!item.containsKey("yearReduceNum") && item.containsKey("yearEditNum")) {
item.set("yearReduceNum", item.get("yearEditNum"));
}
})
.collect(Collectors.toList());
}
/**
* 将V3工作总结附件的文件主键包装为V4文件预览组件所需的response.data格式。
*
@@ -725,7 +747,8 @@ public class SysClubExamineServiceImpl extends BaseServiceImpl<SysClubExamineReg
*/
private void buildPageBaseCondition(Cnd cnd, ClubUserPageForm pageForm, String tableAlias) {
if (pageForm.getYear() != null) {
cnd.andEX("YEAR(" + tableAlias + ".registerDate)", "=", pageForm.getYear());
// 年度查询应与列表展示的登记年度保持一致,不能使用申请时间推算年度。
cnd.andEX(tableAlias + ".year", "=", pageForm.getYear());
}
if (StrUtil.isNotBlank(pageForm.getClubId())) {
cnd.andEX(tableAlias + ".clubId", "=", pageForm.getClubId());
@@ -17,6 +17,10 @@ public class ClubEvaluatePageVo {
private String foundTime;
private String typeName;
private Date applyTime;
/** 社团评优申报开始年度。 */
private String applyStartYear;
/** 社团评优申报结束年度。 */
private String applyEndYear;
private String instanceId;
private String businessNo;
@@ -11,6 +11,7 @@ import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.nutz.dao.Cnd;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.util.NutMap;
@@ -43,22 +44,26 @@ public class ArticleAnalysisController {
@At
@SaCheckPermission("article.analysis")
@ApiOperation(value = "统计分析")
public Result overview() {
public Result overview(Date startDate, Date endDate, String unionId, String clubId) {
List<ArticleAnalysisOverviewVO> list = new ArrayList<>();
// 获取总投稿量
list.add(ArticleAnalysisOverviewVO.builder()
.label("总投稿量")
.value(articleService.count())
.value(articleService.count(buildStatisticsCondition(startDate, endDate, unionId, clubId)))
.icon("el-icon-document")
.bgColor("rgba(24, 103, 176, 0.1)")
.trend(null)
.build());
// 获取本月投稿量
long monthlyCount = articleService.count(Cnd.where("MONTH(submitTime)", "=", DateUtil.thisMonth() + 1));
Cnd monthlyCondition = buildStatisticsCondition(startDate, endDate, unionId, clubId);
monthlyCondition.and("MONTH(submitTime)", "=", DateUtil.thisMonth() + 1);
long monthlyCount = articleService.count(monthlyCondition);
//上月投稿量
long lastMonthCount = articleService.count(Cnd.where("MONTH(submitTime)", "=", DateUtil.offsetMonth(new Date(), -1)));
Cnd lastMonthCondition = buildStatisticsCondition(startDate, endDate, unionId, clubId);
lastMonthCondition.and("MONTH(submitTime)", "=", DateUtil.month(DateUtil.offsetMonth(new Date(), -1)) + 1);
long lastMonthCount = articleService.count(lastMonthCondition);
list.add(ArticleAnalysisOverviewVO.builder()
.label("本月投稿")
@@ -70,7 +75,9 @@ public class ArticleAnalysisController {
// 获取活跃投稿人数
DateTime dateTime = DateUtil.offsetMonth(new Date(), -1);
long activeUsers = Math.max(articleService.count(Cnd.where("submitTime", ">=", dateTime).groupBy("userId")), 0L);
Cnd activeUserCondition = buildStatisticsCondition(startDate, endDate, unionId, clubId);
activeUserCondition.and("submitTime", ">=", dateTime).groupBy("userId");
long activeUsers = Math.max(articleService.count(activeUserCondition), 0L);
list.add(ArticleAnalysisOverviewVO.builder()
.label("活跃投稿人")
.value(activeUsers)
@@ -81,7 +88,11 @@ public class ArticleAnalysisController {
// 获取参与分工会数量
long unionCount = articleService.count(Sqls.create("SELECT COUNT(distinct unionId) FROM article WHERE unionId IS NOT NULL"));
Sql unionCountSql = Sqls.create("SELECT COUNT(distinct unionId) FROM article $condition");
Cnd unionCountCondition = buildStatisticsCondition(startDate, endDate, unionId, clubId);
unionCountCondition.and("unionId", "IS NOT", null);
unionCountSql.setCondition(unionCountCondition);
long unionCount = articleService.count(unionCountSql);
list.add(ArticleAnalysisOverviewVO.builder()
.label("参与分工会")
.value(unionCount)
@@ -91,7 +102,11 @@ public class ArticleAnalysisController {
.build());
// 获取参与社团数量
long clubCount = articleService.count(Sqls.create("SELECT COUNT(distinct clubId) FROM article WHERE clubId IS NOT NULL"));
Sql clubCountSql = Sqls.create("SELECT COUNT(distinct clubId) FROM article $condition");
Cnd clubCountCondition = buildStatisticsCondition(startDate, endDate, unionId, clubId);
clubCountCondition.and("clubId", "IS NOT", null);
clubCountSql.setCondition(clubCountCondition);
long clubCount = articleService.count(clubCountSql);
list.add(ArticleAnalysisOverviewVO.builder()
.label("参与社团")
.value(clubCount)
@@ -102,22 +117,43 @@ public class ArticleAnalysisController {
return Result.success(list);
}
/**
* 构建概览统计的筛选条件,与图表接口保持相同的时间范围、分工会和社团口径。
*/
private Cnd buildStatisticsCondition(Date startDate, Date endDate, String unionId, String clubId) {
Cnd cnd = Cnd.NEW();
if (startDate != null && endDate != null) {
cnd.and(Cnd.cri().where().andBetween("submitTime", startDate, endDate));
}
cnd.andEX("unionId", "=", unionId);
cnd.andEX("clubId", "=", clubId);
return cnd;
}
@At
@SaCheckPermission("article.analysis")
@ApiOperation(value = "投稿趋势")
public Result trend(@Valid String type, Date startDate, Date endDate) {
List<NutMap> submissionTrend = articleAnalysisService.getSubmissionTrend(type, startDate, endDate);
public Result trend(@Valid String type, Date startDate, Date endDate, String unionId, String clubId) {
List<NutMap> submissionTrend = articleAnalysisService.getSubmissionTrend(type, startDate, endDate, unionId, clubId);
return Result.success(submissionTrend);
}
@At
@SaCheckPermission("article.analysis")
@ApiOperation(value = "分工会投稿分布")
public Result distribution(Date startDate, Date endDate) {
List<NutMap> unionDistribution = articleAnalysisService.getUnionDistribution(startDate, endDate);
public Result distribution(Date startDate, Date endDate, String unionId, String clubId) {
List<NutMap> unionDistribution = articleAnalysisService.getUnionDistribution(startDate, endDate, unionId, clubId);
return Result.success(unionDistribution);
}
@At
@SaCheckPermission("article.analysis")
@ApiOperation(value = "社团投稿分布")
public Result clubDistribution(Date startDate, Date endDate, String unionId, String clubId) {
List<NutMap> clubDistribution = articleAnalysisService.getClubDistribution(startDate, endDate, unionId, clubId);
return Result.success(clubDistribution);
}
@At
@SaCheckPermission("article.analysis")
@ApiOperation(value = "投稿排名")
@@ -52,6 +52,7 @@ public class ArticleMineController {
@SaCheckPermission("article.mine")
@ApiOperation("分页查询")
public Result pageData(@Valid ArticleInfoPageParam pageForm) {
// 每篇投稿仅关联最新的一条待办任务,避免多办理人或并行待办将同一投稿重复展示。
Sql sql = Sqls.create("""
SELECT
info.id,
@@ -79,8 +80,11 @@ public class ArticleMineController {
FROM
article info
LEFT JOIN `wf_process_instance` ins ON ins.businessNo = info.id
LEFT JOIN wf_process_task t ON t.processInstanceId = ins.id AND t.taskState = 10
LEFT JOIN wf_process_task_actor ta ON ta.processTaskId = t.id
LEFT JOIN wf_process_task t ON t.id = (
SELECT MAX(currentTask.id)
FROM wf_process_task currentTask
WHERE currentTask.processInstanceId = ins.id AND currentTask.taskState = 10
)
$condition
""");
Cnd cnd = Cnd.NEW();
@@ -13,12 +13,17 @@ public interface ArticleAnalysisService extends BaseService<Article> {
/**
* 获取投稿趋势数据
*/
List<NutMap> getSubmissionTrend(String type, Date startDate, Date endDate);
List<NutMap> getSubmissionTrend(String type, Date startDate, Date endDate, String unionId, String clubId);
/**
* 获取分工会投稿分布
*/
List<NutMap> getUnionDistribution(Date startDate, Date endDate);
List<NutMap> getUnionDistribution(Date startDate, Date endDate, String unionId, String clubId);
/**
* 获取社团投稿分布
*/
List<NutMap> getClubDistribution(Date startDate, Date endDate, String unionId, String clubId);
/**
* 获取排行榜数据
@@ -22,7 +22,7 @@ public class ArticleAnalysisServiceImpl extends BaseServiceImpl<Article> impleme
}
@Override
public List<NutMap> getSubmissionTrend(String type, Date startDate, Date endDate) {
public List<NutMap> getSubmissionTrend(String type, Date startDate, Date endDate, String unionId, String clubId) {
String groupFormat = switch (type) {
case "week" -> "%Y-%u"; // 按周分组
case "month" -> "%Y-%m"; // 按月分组
@@ -40,10 +40,8 @@ public class ArticleAnalysisServiceImpl extends BaseServiceImpl<Article> impleme
""");
sql.setParam("groupFormat",groupFormat);
Cnd cnd = Cnd.NEW();
if(ObjectUtil.isAllNotEmpty(startDate,endDate)){
cnd.and(Cnd.cri().where().andBetween("submitTime", startDate, endDate));
}
Cnd cnd = buildStatisticsCondition(startDate, endDate, unionId, clubId);
cnd.groupBy("DATE_FORMAT(submitTime, @groupFormat)");
sql.setCondition(cnd);
return listMap(sql);
@@ -53,7 +51,7 @@ public class ArticleAnalysisServiceImpl extends BaseServiceImpl<Article> impleme
* 获取分工会投稿分布
*/
@Override
public List<NutMap> getUnionDistribution(Date startDate, Date endDate) {
public List<NutMap> getUnionDistribution(Date startDate, Date endDate, String unionId, String clubId) {
Sql sql = Sqls.create("""
SELECT
unionName AS type,
@@ -62,10 +60,7 @@ public class ArticleAnalysisServiceImpl extends BaseServiceImpl<Article> impleme
article
$condition
""");
Cnd cnd = Cnd.NEW();
if(ObjectUtil.isAllNotEmpty(startDate,endDate)){
cnd.and(Cnd.cri().where().andBetween("submitTime", startDate, endDate));
}
Cnd cnd = buildStatisticsCondition(startDate, endDate, unionId, clubId);
cnd.groupBy("unionId","unionName");
cnd.desc("value");
sql.setCondition(cnd);
@@ -73,6 +68,47 @@ public class ArticleAnalysisServiceImpl extends BaseServiceImpl<Article> impleme
return listMap(sql);
}
/**
* 获取社团投稿分布
*/
@Override
public List<NutMap> getClubDistribution(Date startDate, Date endDate, String unionId, String clubId) {
Sql sql = Sqls.create("""
SELECT
clubName AS type,
COUNT(*) AS value
FROM
article
$condition
""");
Cnd cnd = buildStatisticsCondition(startDate, endDate, unionId, clubId);
cnd.and("clubId", "IS NOT", null);
cnd.groupBy("clubId", "clubName");
cnd.desc("value");
sql.setCondition(cnd);
return listMap(sql);
}
/**
* 为统计图表统一拼接时间范围和分工会筛选条件,保证各图表的查询口径一致。
*
* @param startDate 查询开始日期,为空时不限制开始时间
* @param endDate 查询结束日期,为空时不限制结束时间
* @param unionId 分工会 ID,为空时查询全部分工会
* @param clubId 社团 ID,为空时查询全部社团
* @return 可直接设置到统计 SQL 的查询条件
*/
private Cnd buildStatisticsCondition(Date startDate, Date endDate, String unionId, String clubId) {
Cnd cnd = Cnd.NEW();
if (ObjectUtil.isAllNotEmpty(startDate, endDate)) {
cnd.and(Cnd.cri().where().andBetween("submitTime", startDate, endDate));
}
cnd.andEX("unionId", "=", unionId);
cnd.andEX("clubId", "=", clubId);
return cnd;
}
/**
* 获取排行榜数据
*/
@@ -58,6 +58,10 @@ public class BuildHomeKpiWriteDynamicTableRenderPolicy extends DynamicTableRende
// 处理合并
if(ObjectUtil.isNotEmpty(dataList)){
for (int[] value : groupRanges.values()) {
// 单条明细无需合并;POI 不支持对同一行进行纵向合并。
if (value[0] == value[1]) {
continue;
}
TableTools.mergeCellsVertically(xwpfTable, 0, value[0] + 1, value[1] + 1);
TableTools.mergeCellsVertically(xwpfTable, 1, value[0] + 1, value[1] + 1);
TableTools.mergeCellsVertically(xwpfTable, 4, value[0] + 1, value[1] + 1);
@@ -94,7 +94,7 @@ public class BuildHomeKpiWriteController {
buildHomeKpiWriteService.exportDocx(id, os);
// 下载生成的文件
CommonDownloadUtil.download(
"南京邮电大学分工会教职工之家建设考核评议自评表.docx",
"中国地质大学分工会教职工之家建设考核评议自评表.docx",
os.toByteArray(),
response
);
@@ -136,7 +136,7 @@ public class BuildHomeLogBookController {
template.render(map).writeAndClose(os);
// 下载生成的文件
CommonDownloadUtil.download(
"南京邮电大学分工会建家记录册.docx",
"中国地质大学分工会建家记录册.docx",
os.toByteArray(),
response
);
@@ -2,6 +2,7 @@ package com.budwk.app.zhgh.dayofficework.buildHome.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.result.Result;
@@ -104,8 +105,13 @@ public class BuildHomeMaterialQueryController {
}
@At
@Ok("void")
@SaCheckPermission("buildHome.material.query")
public void exportZip(String actId, HttpServletResponse response) throws IOException {
BuildHomeAct act = buildHomeActService.fetch(actId);
if (act == null) {
throw new IllegalArgumentException("未找到评比事项");
}
Sql sql = Sqls.create("""
SELECT
t1.id,
@@ -123,7 +129,6 @@ public class BuildHomeMaterialQueryController {
AND t2.actId = @actId
LEFT JOIN build_home_act t3 ON t3.id = t2.actId
AND t3.id = @actId
WHERE t1.id = '3742a919fb6847f7a8dbe051a2ef0025'
GROUP BY
t1.id,
t2.unionId
@@ -135,7 +140,9 @@ public class BuildHomeMaterialQueryController {
// 2. 设置响应头
response.setContentType("application/zip");
response.setHeader("Content-Disposition", "attachment; filename=\"工会文件汇总.zip\"");
// 使用评比事项名称作为压缩包名称,避免下载文件无法区分来源。
String zipFileName = sanitizeFileName(act.getName()) + "汇总材料.zip";
response.setHeader("Content-Disposition", "attachment; filename=" + URLUtil.encode(zipFileName));
// 3. 使用try-with-resources创建ZIP输出流
try (ZipOutputStream zos = new ZipOutputStream(response.getOutputStream())) {
@@ -164,6 +171,18 @@ public class BuildHomeMaterialQueryController {
}
/**
* 清理评比事项名称中的 Windows 文件名非法字符,确保浏览器可正常保存压缩包。
*
* @param fileName 原始评比事项名称
* @return 可用于下载文件名的评比事项名称
*/
private String sanitizeFileName(String fileName) {
String sanitizedFileName = StrUtil.blankToDefault(fileName, "评比事项");
return sanitizedFileName.replaceAll("[\\\\/:*?\"<>|]", "_");
}
private void processFileList(ZipOutputStream zos, String unionName, String fileType, String filesJson) throws IOException {
if (StrUtil.isNotBlank(filesJson)) {
List<NutMap> files = Json.fromJsonAsList(NutMap.class, filesJson);
@@ -67,8 +67,8 @@ public class CadreTrainingSignUpController {
@SaCheckPermission("cadreTraining.branchUnionSignUp")
@ApiOperation("分页查询")
public Result pageData(@Valid PageForm pageForm,Integer year, boolean isEnrolled) {
// 同一活动存在多条代报名记录时,活动列表仅展示一次。
Sql sql = Sqls.create("""
-- 同一活动存在多条代报名记录时,活动列表仅展示一次。
SELECT DISTINCT
info.*,
CASE WHEN cts.id IS NOT NULL THEN 1 ELSE 0 END AS isEnrolled
@@ -84,12 +84,26 @@ public class ProposalConfigUnitController {
u2.username AS unitLeader
FROM
proposal_undertake t1
LEFT JOIN sys_user_role sur1 ON sur1.underTakeId = t1.id
AND sur1.roleId = ( SELECT r.id FROM sys_role r WHERE r.CODE = @PROPOSAL_BRANCH_SCHOOL_LEADER )
LEFT JOIN sys_user u1 ON u1.id = sur1.userId
LEFT JOIN sys_user_role sur2 ON sur2.underTakeId = t1.id
AND sur2.roleId = ( SELECT r.id FROM sys_role r WHERE r.CODE = @PROPOSAL_UNIT_LEADER)
LEFT JOIN sys_user u2 ON u2.id = sur2.userId
LEFT JOIN (
SELECT
sur.underTakeId,
GROUP_CONCAT(DISTINCT u.username ORDER BY u.username SEPARATOR '、') AS username,
GROUP_CONCAT(DISTINCT u.loginname ORDER BY u.loginname SEPARATOR ',') AS loginname
FROM sys_user_role sur
LEFT JOIN sys_user u ON u.id = sur.userId
WHERE sur.roleId = (SELECT r.id FROM sys_role r WHERE r.CODE = @PROPOSAL_BRANCH_SCHOOL_LEADER)
GROUP BY sur.underTakeId
) u1 ON u1.underTakeId = t1.id
LEFT JOIN (
SELECT
sur.underTakeId,
GROUP_CONCAT(DISTINCT u.username ORDER BY u.username SEPARATOR '、') AS username,
GROUP_CONCAT(DISTINCT u.loginname ORDER BY u.loginname SEPARATOR ',') AS loginname
FROM sys_user_role sur
LEFT JOIN sys_user u ON u.id = sur.userId
WHERE sur.roleId = (SELECT r.id FROM sys_role r WHERE r.CODE = @PROPOSAL_UNIT_LEADER)
GROUP BY sur.underTakeId
) u2 ON u2.underTakeId = t1.id
$condition
""");
sql.setParam("PROPOSAL_BRANCH_SCHOOL_LEADER", RoleConstant.PROPOSAL_BRANCH_SCHOOL_LEADER.name());
@@ -121,7 +135,9 @@ public class ProposalConfigUnitController {
@SLog(tag = "提案", msg = "承办单位同步系统单位")
public Result syncSysUnit() {
Sql sql = Sqls.create("select code from proposal_undertake");
List<Sys_unit> sysUnits = dao.query(Sys_unit.class, Cnd.where(Sys_unit::getUnitcode, "not in", sql).and(Sys_unit::getUnitTypeCode, "=", 1));
// 三位单位编号即提案业务使用的二级单位,不再叠加系统单位层级条件。
List<Sys_unit> sysUnits = dao.query(Sys_unit.class, Cnd.where(Sys_unit::getUnitcode, "not in", sql)
.and("CHAR_LENGTH(TRIM(unitcode))", "=", 3));
List<ProposalUndertake> proposalUndertakes = sysUnits.stream().map(unit -> {
ProposalUndertake proposalUndertake = new ProposalUndertake();
proposalUndertake.setId(unit.getId());
@@ -7,12 +7,21 @@ layout("/layouts/platform.html"){
<guava ref="guava">
<el-card shadow="never">
<search @search="doSearch">
<search-item label="年度">
<search-item label="申报开始年度">
<el-date-picker
placeholder="请选择年度"
placeholder="请选择申报开始年度"
type="year"
style="width: 100%"
v-model="pageForm.year"
v-model="pageForm.applyStartYear"
value-format="yyyy"
></el-date-picker>
</search-item>
<search-item label="申报结束年度">
<el-date-picker
placeholder="请选择申报结束年度"
type="year"
style="width: 100%"
v-model="pageForm.applyEndYear"
value-format="yyyy"
></el-date-picker>
</search-item>
@@ -41,11 +50,8 @@ layout("/layouts/platform.html"){
<span>{{ row.foundTime ? $moment(row.foundTime).format('YYYY-MM-DD') : '无' }}</span>
</template>
</el-table-column>
<el-table-column label="年度" prop="applyYear" sortable show-overflow-tooltip>
<template slot-scope="{row}">
<span>{{ $moment(row.applyTime).format('YYYY') }}</span>
</template>
</el-table-column>
<el-table-column label="申报开始年度" prop="applyStartYear" sortable show-overflow-tooltip></el-table-column>
<el-table-column label="申报结束年度" prop="applyEndYear" sortable show-overflow-tooltip></el-table-column>
<el-table-column label="申请时间" prop="applyTime" sortable show-overflow-tooltip></el-table-column>
<el-table-column label="当前节点" prop="curTaskName" show-overflow-tooltip></el-table-column>
<el-table-column label="审核时间" prop="finishTime" show-overflow-tooltip>
@@ -104,7 +110,8 @@ layout("/layouts/platform.html"){
return {
pageForm: {
clubId: "",
year: this.$moment().format("YYYY"),
applyStartYear: "",
applyEndYear: "",
audit: false
},
viewData: {},
@@ -8,12 +8,21 @@ layout("/layouts/platform.html"){
<template>
<el-card shadow="never">
<search @search="doSearch">
<search-item label="年度">
<search-item label="申报开始年度">
<el-date-picker
placeholder="请选择年度"
placeholder="请选择申报开始年度"
type="year"
style="width: 100%"
v-model="pageForm.year"
v-model="pageForm.applyStartYear"
value-format="yyyy"
></el-date-picker>
</search-item>
<search-item label="申报结束年度">
<el-date-picker
placeholder="请选择申报结束年度"
type="year"
style="width: 100%"
v-model="pageForm.applyEndYear"
value-format="yyyy"
></el-date-picker>
</search-item>
@@ -39,11 +48,8 @@ layout("/layouts/platform.html"){
<span>{{ row.foundTime ? $moment(row.foundTime).format('YYYY-MM-DD') : '' }}</span>
</template>
</el-table-column>
<el-table-column label="年度" prop="applyTime" sortable show-overflow-tooltip>
<template slot-scope="{row}">
<span>{{ $moment(row.applyTime).format('YYYY') }}</span>
</template>
</el-table-column>
<el-table-column label="申报开始年度" prop="applyStartYear" sortable show-overflow-tooltip></el-table-column>
<el-table-column label="申报结束年度" prop="applyEndYear" sortable show-overflow-tooltip></el-table-column>
<el-table-column label="申请时间" prop="applyTime" sortable show-overflow-tooltip></el-table-column>
<el-table-column label="当前节点" prop="taskName" show-overflow-tooltip></el-table-column>
<el-table-column label="流程状态" prop="instanceState" show-overflow-tooltip>
@@ -88,7 +94,8 @@ layout("/layouts/platform.html"){
return {
pageForm: {
clubId: "",
year: this.$moment().format("YYYY")
applyStartYear: "",
applyEndYear: ""
},
clubList: []
}
@@ -7,12 +7,21 @@ layout("/layouts/platform.html"){
<guava ref="guava">
<el-card shadow="never">
<search @search="doSearch">
<search-item label="年度">
<search-item label="申报开始年度">
<el-date-picker
placeholder="请选择年度"
placeholder="请选择申报开始年度"
type="year"
style="width: 100%"
v-model="pageForm.year"
v-model="pageForm.applyStartYear"
value-format="yyyy"
></el-date-picker>
</search-item>
<search-item label="申报结束年度">
<el-date-picker
placeholder="请选择申报结束年度"
type="year"
style="width: 100%"
v-model="pageForm.applyEndYear"
value-format="yyyy"
></el-date-picker>
</search-item>
@@ -41,11 +50,8 @@ layout("/layouts/platform.html"){
<span>{{ row.foundTime ? $moment(row.foundTime).format('YYYY-MM-DD') : '无' }}</span>
</template>
</el-table-column>
<el-table-column label="年度" prop="applyYear" sortable show-overflow-tooltip>
<template slot-scope="{row}">
<span>{{ $moment(row.applyTime).format('YYYY') }}</span>
</template>
</el-table-column>
<el-table-column label="申报开始年度" prop="applyStartYear" sortable show-overflow-tooltip></el-table-column>
<el-table-column label="申报结束年度" prop="applyEndYear" sortable show-overflow-tooltip></el-table-column>
<el-table-column label="申请时间" prop="applyTime" sortable show-overflow-tooltip></el-table-column>
<el-table-column label="当前节点" prop="curTaskName" show-overflow-tooltip></el-table-column>
<el-table-column label="审核时间" prop="finishTime" show-overflow-tooltip>
@@ -104,7 +110,8 @@ layout("/layouts/platform.html"){
return {
pageForm: {
clubId: "",
year: this.$moment().format("YYYY"),
applyStartYear: "",
applyEndYear: "",
audit: false
},
viewData: {},
@@ -7,12 +7,21 @@ layout("/layouts/platform.html"){
<guava ref="guava">
<el-card shadow="never">
<search @search="doSearch">
<search-item label="年度">
<search-item label="申报开始年度">
<el-date-picker
placeholder="请选择年度"
placeholder="请选择申报开始年度"
type="year"
style="width: 100%"
v-model="pageForm.year"
v-model="pageForm.applyStartYear"
value-format="yyyy"
></el-date-picker>
</search-item>
<search-item label="申报结束年度">
<el-date-picker
placeholder="请选择申报结束年度"
type="year"
style="width: 100%"
v-model="pageForm.applyEndYear"
value-format="yyyy"
></el-date-picker>
</search-item>
@@ -41,11 +50,8 @@ layout("/layouts/platform.html"){
<span>{{ row.foundTime ? $moment(row.foundTime).format('YYYY-MM-DD') : '无' }}</span>
</template>
</el-table-column>
<el-table-column label="年度" prop="applyYear" sortable show-overflow-tooltip>
<template slot-scope="{row}">
<span>{{ $moment(row.applyTime).format('YYYY') }}</span>
</template>
</el-table-column>
<el-table-column label="申报开始年度" prop="applyStartYear" sortable show-overflow-tooltip></el-table-column>
<el-table-column label="申报结束年度" prop="applyEndYear" sortable show-overflow-tooltip></el-table-column>
<el-table-column label="申请时间" prop="applyTime" sortable show-overflow-tooltip></el-table-column>
<el-table-column label="当前节点" prop="curTaskName" show-overflow-tooltip></el-table-column>
<el-table-column label="审核时间" prop="finishTime" show-overflow-tooltip>
@@ -104,7 +110,8 @@ layout("/layouts/platform.html"){
return {
pageForm: {
clubId: "",
year: this.$moment().format("YYYY"),
applyStartYear: "",
applyEndYear: "",
audit: false
},
viewData: {},
@@ -199,10 +199,10 @@
}
const viewData = resp.data || {}
this.$set(this, "viewData", viewData)
this.$set(this.viewData, "changeUserNum", viewData.changeUserNum || [])
this.$set(this, "legacyAuditRecords", viewData.legacyAuditRecords || [])
// 三类补充数据独立加载,成员变化统计失败不能阻断成员和审核记录显示
// 历史成员变化直接展示年审保存数据,成员和审核记录继续独立加载
await Promise.all([
this.getClubUserNum(viewData.clubId),
this.getJgUser(viewData.clubId),
this.getDoneTasks()
])
@@ -210,27 +210,6 @@
this.$message.error("查询年审详情失败")
}
},
async getClubUserNum(clubId) {
if (!clubId) {
this.$set(this.viewData, "changeUserNum", [])
return
}
try {
const resp = await this.$axios.post("/platform/club/examine/apply/getClubUserNum", {
clubId: clubId,
year: this.row.year
})
if (resp.code === 0) {
this.$set(this.viewData, "changeUserNum", resp.data || [])
} else {
this.$set(this.viewData, "changeUserNum", [])
this.$message.warning(resp.msg || "查询社团成员变化情况失败")
}
} catch (error) {
this.$set(this.viewData, "changeUserNum", [])
this.$message.warning("查询社团成员变化情况失败")
}
},
async getJgUser(clubId) {
if (!clubId) {
this.$set(this.viewData, "jgUser", [])
@@ -142,8 +142,13 @@ layout("/layouts/platform.html"){
></el-date-picker>
</el-form-item>
<el-form-item label="分工会">
<el-select :clearable="true" v-model="pageForm.department" placeholder="请选择分工会">
<el-option v-for="item in departments" :key="item.id" :label="item.name" :value="item.id"></el-option>
<el-select :clearable="true" v-model="pageForm.unionId" placeholder="请选择分工会">
<el-option v-for="item in unionOptions" :key="item.id" :label="item.name" :value="item.id"></el-option>
</el-select>
</el-form-item>
<el-form-item label="社团">
<el-select :clearable="true" v-model="pageForm.clubId" placeholder="请选择社团">
<el-option v-for="item in clubOptions" :key="item.id" :label="item.clubName" :value="item.id"></el-option>
</el-select>
</el-form-item>
<el-form-item>
@@ -169,12 +174,15 @@ layout("/layouts/platform.html"){
<div class="chart-card">
<div class="chart-header">
<span class="chart-title">分工会投稿分布</span>
<el-tooltip content="数据更新时间:2024-02-24" placement="top">
<i class="el-icon-info"></i>
</el-tooltip>
</div>
<div id="pieChart" class="chart"></div>
</div>
<div class="chart-card">
<div class="chart-header">
<span class="chart-title">社团投稿分布</span>
</div>
<div id="clubPieChart" class="chart"></div>
</div>
</div>
<!-- 排行榜 -->
@@ -208,7 +216,8 @@ layout("/layouts/platform.html"){
<script nonce="${cspNonce!}">
let trendChart,
distributionChart = null
distributionChart,
clubDistributionChart = null
new Vue({
el: "#app",
@@ -216,10 +225,11 @@ layout("/layouts/platform.html"){
return {
statCards: [],
pageForm: {
dateRange: "",
department: ""
dateRange: [],
unionId: "",
clubId: ""
},
departments: [],
unionOptions: [],
clubOptions: [],
trendType: "month",
rankingTab: "person",
@@ -249,15 +259,27 @@ layout("/layouts/platform.html"){
}
},
methods: {
doSearch() {},
resetSearch() {},
doSearch() {
this.initTrendChart()
this.initDistributionChart()
this.initClubDistributionChart()
this.overview()
},
resetSearch() {
this.$set(this.pageForm, "dateRange", [])
this.$set(this.pageForm, "unionId", "")
this.$set(this.pageForm, "clubId", "")
this.doSearch()
},
initTrendChart() {
// 趋势图数据
this.$axios
.post("/platform/article/analysis/trend", {
type: this.trendType,
startDate: this.pageForm.dateRange[0],
endDate: this.pageForm.dateRange[1]
endDate: this.pageForm.dateRange[1],
unionId: this.pageForm.unionId,
clubId: this.pageForm.clubId
})
.then((res) => {
if (res.code === 0) {
@@ -312,7 +334,9 @@ layout("/layouts/platform.html"){
this.$axios
.post("/platform/article/analysis/distribution", {
startDate: this.pageForm.dateRange[0],
endDate: this.pageForm.dateRange[1]
endDate: this.pageForm.dateRange[1],
unionId: this.pageForm.unionId,
clubId: this.pageForm.clubId
})
.then((res) => {
if (res.code === 0) {
@@ -333,6 +357,32 @@ layout("/layouts/platform.html"){
})
},
initClubDistributionChart() {
this.$axios
.post("/platform/article/analysis/clubDistribution", {
startDate: this.pageForm.dateRange[0],
endDate: this.pageForm.dateRange[1],
unionId: this.pageForm.unionId,
clubId: this.pageForm.clubId
})
.then((res) => {
if (res.code === 0) {
clubDistributionChart && clubDistributionChart.destroy()
clubDistributionChart = new G2Plot.Pie("clubPieChart", {
data: res.data,
angleField: "value",
colorField: "type",
radius: 0.8,
label: {
type: "outer",
content: "{name} {percentage}"
}
})
clubDistributionChart.render()
}
})
},
loadRankingData() {
this.rankingData =
this.rankingTab === "person"
@@ -361,7 +411,12 @@ layout("/layouts/platform.html"){
},
overview() {
this.$axios.post("/platform/article/analysis/overview").then((res) => {
this.$axios.post("/platform/article/analysis/overview", {
startDate: this.pageForm.dateRange[0],
endDate: this.pageForm.dateRange[1],
unionId: this.pageForm.unionId,
clubId: this.pageForm.clubId
}).then((res) => {
if (res.code === 0) {
this.statCards = res.data
}
@@ -371,12 +426,13 @@ layout("/layouts/platform.html"){
mounted() {
this.initTrendChart()
this.initDistributionChart()
this.initClubDistributionChart()
this.loadRankingData()
this.overview()
},
created() {
this.$businessTool.listClub().then((res) => (this.clubOptions = res))
this.$businessTool.listUnit().then((res) => (this.departments = res))
this.$businessTool.listClub().then((res) => this.$set(this, "clubOptions", res))
this.$businessTool.listUnion().then((res) => this.$set(this, "unionOptions", res))
}
})
</script>
@@ -34,6 +34,159 @@ layout("/layouts/platform.html"){
<material-info ref="materialInfoRef"></material-info>
</div>
<style nonce="${cspNonce!}">
.material-write-dialog,
.material-view-dialog {
max-width: 1080px;
margin-top: 7vh !important;
border-radius: 4px;
}
.material-write-dialog .el-dialog__header,
.material-view-dialog .el-dialog__header {
padding: 14px 16px;
border-bottom: 1px solid #ebeef5;
}
.material-write-dialog .el-dialog__title,
.material-view-dialog .el-dialog__title {
font-size: 15px;
font-weight: 600;
color: #303133;
}
.material-write-dialog .el-dialog__body,
.material-view-dialog .el-dialog__body {
max-height: calc(86vh - 112px);
padding: 14px 16px 16px;
overflow-y: auto;
}
.material-write-dialog .time-warning {
margin: 0 0 12px;
padding: 10px 12px;
border: 1px solid #ffd8d8;
border-left: 3px solid #f56c6c;
border-radius: 3px;
background: #fff7f7;
color: #cf1322;
font-size: 13px;
line-height: 20px;
}
.material-write-dialog .upload-list,
.material-view-dialog .upload-list {
margin-bottom: 12px;
}
.material-write-dialog .upload-item,
.material-view-dialog .upload-item {
margin-bottom: 12px;
padding: 14px;
border: 1px solid #ebeef5;
border-radius: 6px;
background: #ffffff;
transition: border-color 0.2s, box-shadow 0.2s;
}
.material-write-dialog .upload-item:first-child,
.material-view-dialog .upload-item:first-child {
border-color: #5b9ef4;
}
.material-write-dialog .upload-item:hover,
.material-view-dialog .upload-item:hover {
border-color: #5b9ef4;
box-shadow: 0 2px 8px rgba(64, 158, 255, 0.1);
}
.material-write-dialog .upload-item-title,
.material-view-dialog .upload-item-title {
display: flex;
align-items: center;
gap: 7px;
margin-bottom: 10px;
color: #303133;
font-size: 14px;
font-weight: 600;
line-height: 20px;
}
.material-write-dialog .upload-item-title i,
.material-view-dialog .upload-item-title i {
color: #1677c8;
}
.material-write-dialog .upload-hint {
margin-bottom: 10px;
padding: 8px 10px;
border-left: 2px solid #409eff;
border-radius: 2px;
background: #eff6ff;
color: #5f6b7a;
font-size: 13px;
line-height: 20px;
}
.material-write-dialog .upload-hint a {
color: #1677c8;
font-weight: 600;
text-decoration: none;
}
.material-write-dialog .upload-hint a:hover {
text-decoration: underline;
}
.material-write-dialog .el-upload__tip {
margin-top: 7px;
color: #909399;
font-size: 12px;
line-height: 18px;
}
.material-write-dialog .notice-box {
margin: 14px 0 0;
padding: 10px 12px;
border: 1px solid #ffe3a0;
border-left: 3px solid #e6a23c;
border-radius: 3px;
background: #fff9e8;
color: #7a5a17;
font-size: 13px;
line-height: 20px;
}
.material-write-dialog .notice-box i {
margin-right: 6px;
color: #e6a23c;
}
.material-write-dialog .el-dialog__footer {
padding: 10px 16px;
border-top: 1px solid #ebeef5;
}
@media screen and (max-width: 768px) {
.material-write-dialog,
.material-view-dialog {
width: calc(100% - 24px) !important;
margin-top: 12px !important;
}
.material-write-dialog .el-dialog__body,
.material-view-dialog .el-dialog__body {
max-height: calc(100vh - 118px);
padding: 12px;
}
.material-write-dialog .upload-item,
.material-view-dialog .upload-item {
padding: 12px;
}
}
</style>
<script nonce="${cspNonce!}">
<!--#include("write.js"){}#-->
<!--#include("./query/info.js"){}#-->
@@ -14,7 +14,7 @@ layout("/layouts/platform.html"){
</el-card>
<el-card shadow="never" class="mt10">
<table-tool label="数据列表">
<el-button size="small" type="primary" icon="el-icon-download" @click="exportZip">导出汇总表</el-button>
<el-button size="small" type="primary" icon="el-icon-download" @click="exportZip">导出汇总表Zip</el-button>
</table-tool>
<el-table :data="tableData" :size="tableSize" @sort-change="pageOrder" ref="table" row-key="id" style="width: 100%">
<el-table-column :index="indexMethod" align="center" header-align="center" label="序号" type="index" width="80px"></el-table-column>
@@ -71,6 +71,10 @@ layout("/layouts/platform.html"){
})
},
exportZip(){
if (!this.pageForm.actId) {
this.$message.warning("请先选择评比事项")
return
}
this.$downLoad('/platform/buildHome/material/query/exportZip', this.pageForm)
}
},
@@ -1,6 +1,6 @@
const info = {
template: /*language=HTML*/ `
<el-dialog title="查看详情" :visible.sync="visible" width="50%">
<el-dialog title="查看详情" :visible.sync="visible" width="80%" custom-class="material-view-dialog">
<div class="upload-list">
<div class="upload-item">
<div class="upload-item-title">
@@ -1,6 +1,6 @@
const write = {
template: /*language=HTML*/ `
<el-dialog :title="dialogTitle" :visible.sync="visible" width="70%">
<el-dialog :title="dialogTitle" :visible.sync="visible" width="80%" custom-class="material-write-dialog">
<div class="time-warning" v-if="activity && !isWithinTimeRange">
当前时间不在提交时间范围内无法提交材料提交时间
@@ -94,8 +94,6 @@ const write = {
<strong>重要提示</strong>
建家记录册教职工之家建设考核评议自评表需由党委主要负责人签字盖章后扫描件上传
</div>
</div>
<div slot="footer" class="dialog-footer">
<el-button @click="visible = false"> </el-button>
<el-button type="primary" @click="onSubmit" :disabled="!isWithinTimeRange">提交评比材料</el-button>
@@ -28,7 +28,7 @@ layout("/layouts/platform.html"){
<el-table-column prop="name" label="单位名称" sortable="custom" show-overflow-tooltip></el-table-column>
<el-table-column prop="code" label="单位编码" sortable="custom" width="150px"></el-table-column>
<!-- <el-table-column prop="branchSchoolLeader" label="分管校领导及联系方式" width="180px"></el-table-column>-->
<el-table-column prop="unitLeader" label="单位领导及联系方式" sortable="custom" width="180px" show-overflow-tooltip></el-table-column>
<el-table-column prop="unitLeader" label="单位领导" sortable="custom" width="180px" show-overflow-tooltip></el-table-column>
<el-table-column label="操作" width="350px">
<template slot-scope="{row}">
<!-- <el-button size="mini" type="primary" @click="$refs.userRef.onOpen(row.id,true)">设置分管校领导</el-button>-->
@@ -62,7 +62,10 @@ const pushAttendanceDialog = {
}
},
filterMethod(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
},
async onConfirm() {
const loading = createLoading('正在提交中')
@@ -79,7 +79,10 @@ const pushFormalDialog = {
}
},
filterMethod(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
},
async onConfirm() {
const loading = createLoading('正在提交中')