!117 feat(bpm): 新增流程节点驳回目标范围控制功能

* feat(bpm): 新增流程节点驳回目标范围控制功能
This commit is contained in:
wsm
2026-07-27 06:30:12 +00:00
parent 31d897e2a5
commit 3698dda2a8
4 changed files with 135 additions and 7 deletions
@@ -34,6 +34,7 @@
<script lang="ts"> <script lang="ts">
import { taskTransInfo, taskComplete, taskClaim } from '../task.handle.api'; import { taskTransInfo, taskComplete, taskClaim } from '../task.handle.api';
import { queryRejectTargetCodes } from '../../../processDesign/processNode/process.node.api';
import { ref, watch, computed } from 'vue'; import { ref, watch, computed } from 'vue';
import TaskNodeStepList from './TaskNodeStepList.vue'; import TaskNodeStepList from './TaskNodeStepList.vue';
import TaskCommentList from './TaskCommentList.vue'; import TaskCommentList from './TaskCommentList.vue';
@@ -177,6 +178,17 @@
currentNode.value = nodeObject; currentNode.value = nodeObject;
historyList.value = data.histListNode; historyList.value = data.histListNode;
// 驳回目标节点过滤:若配置了可驳回目标节点,则前端过滤历史节点;查询失败时不阻塞,展示全部
if (data.rejectStatus == '1') {
try {
const allowedCodes = await queryRejectTargetCodes({ processKey: data.processKey, nodeCode: data.taskDefinitionKey });
if (allowedCodes && allowedCodes.length > 0) {
historyList.value = historyList.value.filter(item => allowedCodes.includes(item.TASK_DEF_KEY_));
}
} catch {
// 查询失败时不阻塞审批流程,显示所有历史节点
}
}
branchList.value = data.transitionList; branchList.value = data.transitionList;
turnbackTaskId.value = data.turnbackTaskId; turnbackTaskId.value = data.turnbackTaskId;
@@ -6,7 +6,10 @@ enum Api {
deleteBatch = '/act/process/extActProcessNode/deleteBatch', deleteBatch = '/act/process/extActProcessNode/deleteBatch',
add = '/act/process/extActProcessNode/add', add = '/act/process/extActProcessNode/add',
edit = '/act/process/extActProcessNode/edit', edit = '/act/process/extActProcessNode/edit',
batchSavePermission = '/act/process/extActProcessNodePermission/saveOrUpdateBatch' batchSavePermission = '/act/process/extActProcessNodePermission/saveOrUpdateBatch',
queryRejectTargets = '/act/process/extActProcessNode/queryRejectTargets',
saveRejectTargets = '/act/process/extActProcessNode/saveRejectTargets',
queryRejectTargetCodes = '/act/process/extActProcessNode/queryRejectTargetCodes',
} }
/** /**
@@ -39,3 +42,24 @@ export const saveOrUpdate = (params, isUpdate) => {
export const batchSavePermission = (params) => { export const batchSavePermission = (params) => {
return defHttp.post({ url: Api.batchSavePermission, params }); return defHttp.post({ url: Api.batchSavePermission, params });
} }
/**
* 查询节点的驳回目标配置
* @param params { processId, sourceNodeCode }
*/
export const queryRejectTargets = (params) =>
defHttp.get({ url: Api.queryRejectTargets, params });
/**
* 保存节点的驳回目标配置
* @param queryParams - { processId, sourceNodeCode }
* @param bodyData - array of { targetNodeCode, targetNodeName }
*/
export const saveRejectTargets = (queryParams, bodyData) =>
defHttp.post({ url: Api.saveRejectTargets, params: queryParams, data: bodyData }, { isTransformResponse: false });
/**
* 根据 processKey + nodeCode 查询可驳回的目标节点CODE列表(运行时过滤用)
*/
export const queryRejectTargetCodes = (params) =>
defHttp.get({ url: Api.queryRejectTargetCodes, params });
@@ -133,6 +133,31 @@
</span> </span>
</div> </div>
<!-- 驳回目标节点选择仅在"是否允许驳回"开启时显示 -->
<div v-if="buttonStatus.rejectStatus === '1'" style="padding: 0 10px; margin-bottom: 10px;">
<div style="margin-bottom: 8px; font-size: 14px; color: #4b4a4a; font-weight: 500;">
可驳回的目标节点
</div>
<div style="background: #fafafa; border: 1px solid #d9d9d9; border-radius: 6px; padding: 12px;">
<div v-if="allProcessNodes.length === 0" style="color: #999; text-align: center; padding: 10px;">
暂无可选节点
</div>
<a-checkbox-group v-model:value="checkedTargetNodeCodes" v-else>
<div v-for="node in allProcessNodes" :key="node.processNodeCode"
style="margin-bottom: 6px;">
<a-checkbox
:value="node.processNodeCode"
:disabled="node.processNodeCode === processInfo.processNodeCode">
{{ node.processNodeName }}
<span style="color: #999; font-size: 12px;">({{ node.processNodeCode }})</span>
<span v-if="node.processNodeCode === processInfo.processNodeCode"
style="color: #ff4d4f; font-size: 12px;"> [当前节点]</span>
</a-checkbox>
</div>
</a-checkbox-group>
</div>
</div>
</a-tab-pane> </a-tab-pane>
</a-tabs> </a-tabs>
</a-spin> </a-spin>
@@ -154,7 +179,7 @@
import { useMessage } from '/@/hooks/web/useMessage'; import { useMessage } from '/@/hooks/web/useMessage';
import { useAuthField, useAuthButton } from './useNodeAuthEasyModal'; import { useAuthField, useAuthButton } from './useNodeAuthEasyModal';
import { saveOrUpdate, batchSavePermission } from '../processNode/process.node.api'; import { saveOrUpdate, batchSavePermission, saveRejectTargets } from '../processNode/process.node.api';
import NodeAuthEasyForSelect from './NodeAuthEasyForSelect.vue' import NodeAuthEasyForSelect from './NodeAuthEasyForSelect.vue'
import NodeAuthEasyForInput from './NodeAuthEasyForInput.vue' import NodeAuthEasyForInput from './NodeAuthEasyForInput.vue'
import { defHttp } from '/@/utils/http/axios'; import { defHttp } from '/@/utils/http/axios';
@@ -193,7 +218,10 @@
const { formTypeArray, activeFormType, activeTableName, myFormList, handleChangeFormType, queryBizFormType, queryLastAuthData, authDataList } = useAuthField(spinningLoading, processInfo); const { formTypeArray, activeFormType, activeTableName, myFormList, handleChangeFormType, queryBizFormType, queryLastAuthData, authDataList } = useAuthField(spinningLoading, processInfo);
const { buttonStatus, initButtonStatus, handleChangeFormEditStatus } = useAuthButton(activeFormType, activeTableName); const { buttonStatus, initButtonStatus, handleChangeFormEditStatus, rejectTargetList, allProcessNodes } = useAuthButton(activeFormType, activeTableName);
// 驳回目标节点勾选状态
const checkedTargetNodeCodes = ref([]);
//useModalInner //useModalInner
const [registerModal, { closeModal }] = useModalInner(async (data) => { const [registerModal, { closeModal }] = useModalInner(async (data) => {
@@ -204,7 +232,9 @@
processInfo.processNodeCode = processNodeCode; processInfo.processNodeCode = processNodeCode;
activeTableName.value = ''; activeTableName.value = '';
/**--------------按钮----------------*/ /**--------------按钮----------------*/
initButtonStatus(data); await initButtonStatus(data);
// 回填已配置的驳回目标节点勾选状态
checkedTargetNodeCodes.value = rejectTargetList.value.map(r => r.targetNodeCode);
/**--------------字段----------------*/ /**--------------字段----------------*/
//1.查询已经存在的权限数据 ruleName ruleCode formType ruleType status //1.查询已经存在的权限数据 ruleName ruleCode formType ruleType status
@@ -235,7 +265,6 @@
let allIsOk = true; let allIsOk = true;
const nodeUpdateResult = await defHttp.put({ url: '/act/process/extActProcessNode/edit', params: node }, { isTransformResponse: false }); const nodeUpdateResult = await defHttp.put({ url: '/act/process/extActProcessNode/edit', params: node }, { isTransformResponse: false });
spinningLoading.value = false;
// console.log('nodeUpdateResult', nodeUpdateResult) // console.log('nodeUpdateResult', nodeUpdateResult)
if(nodeUpdateResult.success){ if(nodeUpdateResult.success){
//保存成功 //保存成功
@@ -245,12 +274,35 @@
createMessage.warning(nodeUpdateResult.message); createMessage.warning(nodeUpdateResult.message);
} }
// 保存驳回目标节点配置
if (buttonStatus.rejectStatus === '1') {
const rejectTargets = checkedTargetNodeCodes.value.map(targetCode => {
const nodeInfo = allProcessNodes.value.find(n => n.processNodeCode === targetCode);
return {
processId: processInfo.processId,
sourceNodeCode: processInfo.processNodeCode,
targetNodeCode: targetCode,
targetNodeName: nodeInfo ? nodeInfo.processNodeName : '',
};
});
try {
await saveRejectTargets(
{ processId: processInfo.processId, sourceNodeCode: processInfo.processNodeCode },
rejectTargets
);
} catch {
allIsOk = false;
createMessage.warning('驳回目标节点保存失败');
}
}
//节点字段 //节点字段
let data = []; let data = [];
if (activeFormType.value === '3') { if (activeFormType.value === '3') {
//update-begin-author:liusq---date:2024-06-17--for: TV360X-990 代码生成的权限设置的小问题 //update-begin-author:liusq---date:2024-06-17--for: TV360X-990 代码生成的权限设置的小问题
data = await customerTableRef.value.getData(); data = await customerTableRef.value.getData();
if (!data) { if (!data) {
spinningLoading.value = false;
return; return;
} }
//update-end-author:liusq---date:2024-06-17--for: TV360X-990 代码生成的权限设置的小问题 //update-end-author:liusq---date:2024-06-17--for: TV360X-990 代码生成的权限设置的小问题
@@ -272,6 +324,7 @@
createMessage.warning(authUpdateResult.message); createMessage.warning(authUpdateResult.message);
} }
} }
spinningLoading.value = false;
if(allIsOk==true){ if(allIsOk==true){
createMessage.success('操作成功!'); createMessage.success('操作成功!');
} }
@@ -352,6 +405,8 @@
activeKey, activeKey,
buttonStatus, buttonStatus,
authDataList, authDataList,
checkedTargetNodeCodes,
allProcessNodes,
spinningLoading, spinningLoading,
formTypeArray, formTypeArray,
@@ -1,6 +1,7 @@
import { useMessage } from '/@/hooks/web/useMessage'; import { useMessage } from '/@/hooks/web/useMessage';
import { queryFormTypeByProcessId, queryFormBizCodeByProcessid, list } from './node.auth.api'; import { queryFormTypeByProcessId, queryFormBizCodeByProcessid, list } from './node.auth.api';
import { ref, reactive } from 'vue'; import { ref, reactive } from 'vue';
import { list as listProcessNodes, queryRejectTargets } from '../processNode/process.node.api';
/** /**
* 用于字段权限配置 * 用于字段权限配置
@@ -129,11 +130,44 @@ export function useAuthButton(activeFormType, activeTableName) {
}); });
function initButtonStatus(record) { // 驳回目标节点配置
const rejectTargetList = ref<Array<{ processId: string; sourceNodeCode: string; targetNodeCode: string; targetNodeName: string }>>([]);
// 当前流程所有节点
const allProcessNodes = ref<Array<{ processNodeCode: string; processNodeName: string }>>([]);
async function fetchRejectTargets(processId: string, sourceNodeCode: string) {
if (!processId || !sourceNodeCode) return;
try {
const result = await queryRejectTargets({ processId, sourceNodeCode });
rejectTargetList.value = result || [];
} catch {
rejectTargetList.value = [];
}
}
async function fetchAllProcessNodes(processId: string) {
if (!processId) return;
try {
const result = await listProcessNodes({ processId, pageSize: 500 });
allProcessNodes.value = (result.records || []).map((r) => ({
processNodeCode: r.processNodeCode,
processNodeName: r.processNodeName,
}));
} catch {
allProcessNodes.value = [];
}
}
async function initButtonStatus(record) {
Object.keys(buttonStatus).map((k) => { Object.keys(buttonStatus).map((k) => {
console.log(record[k]); console.log(record[k]);
buttonStatus[k] = record[k]; buttonStatus[k] = record[k];
}); });
// 加载驳回目标配置
if (record.processId && record.processNodeCode) {
await fetchAllProcessNodes(record.processId);
await fetchRejectTargets(record.processId, record.processNodeCode);
}
} }
function handleChangeFormEditStatus(value) { function handleChangeFormEditStatus(value) {
@@ -184,5 +218,8 @@ export function useAuthButton(activeFormType, activeTableName) {
buttonStatus, buttonStatus,
initButtonStatus, initButtonStatus,
handleChangeFormEditStatus, handleChangeFormEditStatus,
rejectTargetList,
allProcessNodes,
fetchRejectTargets,
}; };
} }