fix:1、提案查看的流程节点,增加排序

2、场地预约关闭按钮无法关闭的bug修复
This commit is contained in:
2026-09-04 16:15:55 +08:00
committed by c-chengc1
parent 43ae24dceb
commit 6ef45e5403
7 changed files with 193 additions and 11 deletions
@@ -23,6 +23,7 @@ import com.budwk.app.flow.entity.ProcessCcInstance;
import com.budwk.app.flow.entity.ProcessDefine;
import com.budwk.app.flow.entity.ProcessInstance;
import com.budwk.app.flow.entity.ProcessTask;
import com.budwk.app.flow.entity.ProcessTaskActor;
import com.budwk.app.flow.enums.ProcessInstanceStateEnum;
import com.budwk.app.flow.enums.ProcessSubmitTypeEnum;
import com.budwk.app.flow.enums.ProcessTaskStateEnum;
@@ -556,11 +557,18 @@ public class ProcessInstanceServiceImpl extends BaseServiceImpl<ProcessInstance>
public void deleteProcessInstanceById(Long processInstanceId) {
List<ProcessTask> taskList = dao().query(ProcessTask.class, Cnd.where(ProcessTask::getProcessInstanceId, "=", processInstanceId));
List<Long> taskIds = taskList.stream().map(ProcessTask::getId).toList();
dao().clear(ProcessTask.class, Cnd.where(ProcessTask::getId, "in", taskIds));
// 任务参与人依赖流程任务,删除流程时先清理参与人,避免遗留无法回查所属流程的孤立记录。
if (CollectionUtil.isNotEmpty(taskIds)) {
dao().clear(ProcessTaskActor.class, Cnd.where(ProcessTaskActor::getProcessTaskId, "in", taskIds));
dao().clear(ProcessTask.class, Cnd.where(ProcessTask::getId, "in", taskIds));
}
// 抄送记录直接依赖流程实例,需要与实例一并清理。
dao().clear(ProcessCcInstance.class, Cnd.where(ProcessCcInstance::getProcessInstanceId, "=", processInstanceId));
dao().clear(ProcessInstance.class, Cnd.where(ProcessInstance::getId, "=", processInstanceId));
}
@Override
@Aop(TransAop.READ_COMMITTED)
public void deleteProcessInstanceByBusinessKey(String businessKey) {
ProcessInstance processInstance = fetch(Cnd.where(ProcessInstance::getBusinessNo, "=", businessKey));
if (processInstance != null) {
@@ -11,6 +11,7 @@ import com.budwk.app.base.result.Result;
import com.budwk.app.base.utils.PageUtil;
import com.budwk.app.flow.constant.FlowConst;
import com.budwk.app.flow.engine.FlowEngine;
import com.budwk.app.flow.entity.ProcessDefine;
import com.budwk.app.flow.entity.ProcessInstance;
import com.budwk.app.flow.entity.ProcessTask;
import com.budwk.app.flow.enums.ProcessInstanceStateEnum;
@@ -95,6 +96,7 @@ public class ClubRefreshReportController {
sys_club_refresh info
LEFT JOIN sys_club club ON club.id = info.clubId
LEFT JOIN `wf_process_instance` ins ON ins.businessNo = info.id
AND ins.processDefineId IN (SELECT id FROM wf_process_define WHERE name = 'XHHJBG')
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
$condition
@@ -176,10 +178,23 @@ public class ClubRefreshReportController {
@ApiOperation("删除")
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission("club.infoManage.refreshReport")
@SLog(tag = "社团管理系统-信息管理", msg = "删除换届报告")
@SLog(tag = "社团管理系统-信息管理", msg = "删除换届报告")
public Result delete(@Param("id") String id) {
// 历史换届记录可能与社团注册共用业务编号,必须按流程定义精确定位换届流程,避免误删注册流程。
List<Long> refreshDefineIds = dao.query(ProcessDefine.class,
Cnd.where(ProcessDefine::getName, "=", "XHHJBG"))
.stream()
.map(ProcessDefine::getId)
.toList();
List<ProcessInstance> refreshInstances = refreshDefineIds.isEmpty()
? List.of()
: dao.query(ProcessInstance.class,
Cnd.where(ProcessInstance::getBusinessNo, "=", id)
.and(ProcessInstance::getProcessDefineId, "in", refreshDefineIds));
clubInfoManageService.dao().delete(SysClubRefresh.class, id);
flowEngine.processInstanceService().deleteProcessInstanceByBusinessKey(id);
for (ProcessInstance refreshInstance : refreshInstances) {
flowEngine.processInstanceService().deleteProcessInstanceById(refreshInstance.getId());
}
return Result.success();
}
}
@@ -17,6 +17,8 @@ import lombok.extern.slf4j.Slf4j;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.util.cri.SqlExpressionGroup;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.Lang;
@@ -161,6 +163,26 @@ public class SiteCugManageController {
return Result.success(savedInfo == null ? info : savedInfo);
}
/**
* 切换场地开启状态。
*
* @param id 场地ID,必须传入已存在的 site_cug_info 主键
* @param state 目标状态,true 表示开启,false 表示关闭
* @return Result 响应;code 为 0 表示状态更新成功,否则 msg 为参数错误信息
*/
@At
@ApiOperation("切换场地开启状态")
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission("siteCug.manage")
@SLog(tag = "场馆功能管理-场地管理", msg = "切换场地开启状态")
public Result switchState(String id, Boolean state) {
if (StrUtil.isBlank(id) || state == null) {
return Result.error("场地ID或开启状态不能为空");
}
infoService.updateState(id, state);
return Result.success();
}
@At
@ApiOperation("delete")
@SaCheckPermission("siteCug.manage")
@@ -4,4 +4,12 @@ import com.budwk.app.base.service.BaseService;
import com.budwk.app.zhgh.dayofficework.siteCug.model.SiteCugInfo;
public interface SiteCugInfoService extends BaseService<SiteCugInfo> {
/**
* 仅更新场地开启状态,不触发场地完整表单校验。
*
* @param id 场地ID
* @param state true 表示开启,false 表示关闭
*/
void updateState(String id, Boolean state);
}
@@ -4,7 +4,11 @@ import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.zhgh.dayofficework.siteCug.model.SiteCugInfo;
import com.budwk.app.zhgh.dayofficework.siteCug.service.SiteCugInfoService;
import lombok.extern.slf4j.Slf4j;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.dao.Chain;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.IocBean;
@Slf4j
@@ -14,4 +18,16 @@ public class SiteCugInfoServiceImpl extends BaseServiceImpl<SiteCugInfo> impleme
public SiteCugInfoServiceImpl(Dao dao) {
super(dao);
}
/**
* 仅更新指定场地的开启状态,防止开关操作覆盖其他场地配置。
*
* @param id 场地ID
* @param state true 表示开启,false 表示关闭
*/
@Override
@Aop(TransAop.READ_COMMITTED)
public void updateState(String id, Boolean state) {
update(Chain.make("state", state), Cnd.where("id", "=", id));
}
}
@@ -36,7 +36,7 @@ layout("/layouts/platform.html"){
新增场地
</el-button>
</table-tool>
<el-table :data="tableData" @sort-change="pageOrder" :size="tableSize">
<el-table v-loading="tableLoading" :data="tableData" @sort-change="pageOrder" :size="tableSize">
<el-table-column label="序号" type="index" :index="indexMethod" width="60"></el-table-column>
<el-table-column
v-for="column in tableColumns"
@@ -152,11 +152,23 @@ layout("/layouts/platform.html"){
.catch(() => {})
},
switchChange(row) {
this.$axios.post("/platform/siteCug/manage/submit", row).then((res) => {
const previousState = !row.state
this.tableLoading = true
this.$axios.post("/platform/siteCug/manage/switchState", {
id: row.id,
state: row.state
}).then((res) => {
if (res.code === 0) {
this.$message.success(res.msg)
this.pageData()
} else {
// 状态更新失败时恢复开关,避免页面显示与数据库实际状态不一致。
this.$set(row, "state", previousState)
this.$message.warning(res.msg)
}
}).catch(() => {
this.$set(row, "state", previousState)
}).finally(() => {
this.tableLoading = false
})
},
querySiteType() {
@@ -110,10 +110,10 @@ const PROPOSAL_INFO = {
</el-dialog>
<!--审核信息支持按需同时展开多条记录,初次加载和切换提案时仍保持全部折叠。-->
<template v-if="doneTasks.length">
<template v-if="displayDoneTasks.length">
<div class="process-title">审核信息</div>
<el-collapse v-model="activeTaskIds">
<el-collapse-item v-for="task in doneTasks" :key="task.id" :name="task.id">
<el-collapse-item v-for="task in displayDoneTasks" :key="task.id" :name="task.id">
<template slot="title">
<!--折叠标题使用独立的单行布局,避免复用 process-title 后产生外边距和高度冲突。-->
<div style="display: flex;align-items: center;justify-content: space-between;width: 100%;min-width: 0;padding-left: 10px">
@@ -125,8 +125,28 @@ const PROPOSAL_INFO = {
</span>
</div>
</template>
<!--同一提案可能存在多个承办单位答复任务,查看时合并为一个流程节点,节点内保留每条答复。-->
<template v-if="task.replyTasks">
<el-descriptions v-for="replyTask in task.replyTasks" border class="flow-task-form"
:column="3" :key="replyTask.id">
<el-descriptions-item label="承办单位" :span="3">
{{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-if="task.ext.isFirstTaskNode">
v-else-if="task.ext.isFirstTaskNode">
<el-descriptions-item label="申请用户">{{ task.ext.initiatorName
}}({{task.ext.initiatorAccount}})
</el-descriptions-item>
@@ -233,6 +253,7 @@ const PROPOSAL_INFO = {
tableLoading: false,
viewData: {},
doneTasks: [],
displayDoneTasks: [],
activeTaskIds: [],
row: null,
viewDialogVisible: false
@@ -258,6 +279,7 @@ const PROPOSAL_INFO = {
this.$set(this, "tableLoading", true)
// 切换提案时先清空上一条提案的审核记录和展开状态,避免异步加载期间展示旧数据。
this.doneTasks = []
this.displayDoneTasks = []
this.activeTaskIds = []
let pendingRequestCount = 2
const handleRequestComplete = () => {
@@ -293,15 +315,94 @@ const PROPOSAL_INFO = {
return this.$axios.post("/flow/common/doneTasks", {bizId: this.row.id}).then((res) => {
if (res.code === 0) {
const tasks = res.data || []
this.doneTasks = tasks
// 提案历史按业务阶段展示,避免较晚完成的附议记录被排到团长审核之后。
const sortedTasks = this.sortDoneTasks(tasks)
this.$set(this, "doneTasks", sortedTasks)
// 参考历史系统的展示方式,将多个单位的答复收纳在一个流程节点内。
this.$set(this, "displayDoneTasks", this.groupReplyTasks(sortedTasks))
// 审核记录加载完成后默认全部折叠,由用户按需展开查看。
this.activeTaskIds = []
// 通知外层页面已办节点数据已加载完成,便于当前环节做表单回显。
this.$emit("done-tasks", tasks)
this.$emit("done-tasks", this.doneTasks)
}
})
},
/**
* 按提案业务阶段对审核历史排序。
*
* @param tasks doneTasks 接口返回的已办任务数组
* @return 排序后的新数组;同一阶段按办理时间、任务 ID 保持稳定顺序
*/
sortDoneTasks(tasks) {
return (tasks || []).map((task, index) => ({task, index})).sort((left, right) => {
const stageDiff = this.getTaskStageOrder(left.task) - this.getTaskStageOrder(right.task)
if (stageDiff !== 0) {
return stageDiff
}
const leftTime = String(left.task.finishTime || "")
const rightTime = String(right.task.finishTime || "")
if (leftTime !== rightTime) {
if (!leftTime) return 1
if (!rightTime) return -1
return leftTime.localeCompare(rightTime)
}
const idDiff = Number(left.task.id || 0) - Number(right.task.id || 0)
return idDiff !== 0 ? idDiff : left.index - right.index
}).map((item) => item.task)
},
/**
* 将同一提案的承办单位答复任务合并成一个展示节点。
*
* @param tasks 已按业务阶段排好顺序的任务数组
* @return 仅用于查看页面的展示数组;原始任务及每条答复内容均保存在 replyTasks 中
*/
groupReplyTasks(tasks) {
const replyTaskNames = ["opinion_unit_reply", "unit_reply", "two_unit_reply", "master_reply", "slave_reply"]
const replyTasks = (tasks || []).filter((task) => replyTaskNames.includes(task.taskName))
if (!replyTasks.length) {
return tasks || []
}
const firstReplyTask = replyTasks[0]
const replyTaskIds = new Set(replyTasks.map((task) => task.id))
return (tasks || []).reduce((displayTasks, task) => {
if (!replyTaskIds.has(task.id)) {
displayTasks.push(task)
} else if (task.id === firstReplyTask.id) {
displayTasks.push({
id: "reply-group-" + firstReplyTask.id,
taskName: "replyGroup",
displayName: "承办单位答复",
replyTasks: replyTasks
})
}
return displayTasks
}, [])
},
/**
* 获取提案任务所属的展示阶段。
* 未单列的预审核、委员审议、确认承办单位和复核记录统一放在委员会立案阶段,
* 答复后的校领导审批通过 legacyStage 标识排在承办单位答复之后。
*
* @param task 单条已办任务
* @return 数字类型的阶段顺序
*/
getTaskStageOrder(task) {
const taskName = task.taskName
const legacyStage = task.ext ? task.ext.legacyStage : ""
if (taskName === "startTask") return 10
if (taskName === "invite") return 15
if (taskName === "second") return 20
if (taskName === "delegation") return 30
if (["opinion_unit_reply", "unit_reply", "two_unit_reply", "master_reply", "slave_reply"].includes(taskName)) return 50
if (taskName === "schoolLeader" && legacyStage === "replyLeaderAudit") return 55
if (taskName === "feedback") return 60
return 40
},
openChart(){
this.$refs.snakerChartRef.onOpenFull(this.row.instanceProcessDefineId, this.row.instanceId)
}