feat(bpm): 流程模型列表新增「免审批」开关与全局总开关
- 流程模型列表新增「免审批」列,行内切换单个流程是否跳过 OA 审批
- 页面右上角新增「全部免审批」总开关,开启后所有流程一律免审批(优先于各流程自己的开关)
- 无 bpm:process-auto-approve:update 权限时开关置灰;流程未发布时给出「开关不会生效」提示
Co-Authored-By: Claude <noreply@anthropic.com>
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import { requestClient } from '#/api/request'; |
| | | |
| | | export namespace BpmAutoApproveApi { |
| | | /** æµç¨å
审æ¹é
ç½® */ |
| | | export interface AutoApproveConfig { |
| | | id: number; |
| | | /** æµç¨å®ä¹ç Key */ |
| | | processDefinitionKey: string; |
| | | /** æ¯å¦å
审æ¹ï¼true-æäº¤åç³»ç»èªå¨éè¿ï¼false-æ£å¸¸äººå·¥å®¡æ¹ */ |
| | | autoApprove: boolean; |
| | | /** 夿³¨ */ |
| | | remark?: string; |
| | | createTime: string; |
| | | } |
| | | } |
| | | |
| | | /** è·åå
¨é¨æµç¨å
审æ¹é
ç½® */ |
| | | export async function getAutoApproveList() { |
| | | return requestClient.get<BpmAutoApproveApi.AutoApproveConfig[]>( |
| | | '/bpm/process-auto-approve/list', |
| | | ); |
| | | } |
| | | |
| | | /** è·å¾å
¨å±å
å®¡æ¹æ»å¼å
³ç¶æ */ |
| | | export async function getGlobalAutoApprove() { |
| | | return requestClient.get<boolean>('/bpm/process-auto-approve/global'); |
| | | } |
| | | |
| | | /** 设置å
¨å±å
å®¡æ¹æ»å¼å
³ï¼å¼å¯åæææµç¨ä¸å¾å
å®¡æ¹ */ |
| | | export async function updateGlobalAutoApprove( |
| | | autoApprove: boolean, |
| | | remark?: string, |
| | | ) { |
| | | const params = new URLSearchParams({ autoApprove: String(autoApprove) }); |
| | | if (remark) { |
| | | params.append('remark', remark); |
| | | } |
| | | return requestClient.put<boolean>( |
| | | `/bpm/process-auto-approve/update-global?${params.toString()}`, |
| | | ); |
| | | } |
| | | |
| | | /** ææµç¨å®ä¹ç Key 设置å
审æ¹å¼å
³ï¼ä¸åå¨åæ°å¢ãåå¨åæ´æ°ï¼ */ |
| | | export async function updateAutoApproveByKey( |
| | | processDefinitionKey: string, |
| | | autoApprove: boolean, |
| | | remark?: string, |
| | | ) { |
| | | const params = new URLSearchParams({ |
| | | processDefinitionKey, |
| | | autoApprove: String(autoApprove), |
| | | }); |
| | | if (remark) { |
| | | params.append('remark', remark); |
| | | } |
| | | return requestClient.put<boolean>( |
| | | `/bpm/process-auto-approve/update-by-key?${params.toString()}`, |
| | | ); |
| | | } |
| | |
| | | createTime: string; |
| | | bpmnXml: string; |
| | | startUsers?: UserInfo[]; |
| | | /** æ¯å¦å
审æ¹ï¼ç±é¡µé¢æ ¹æ®æµç¨å
审æ¹é
ç½®åå¹¶ï¼éå端è¿ååæ®µï¼ */ |
| | | autoApprove?: boolean; |
| | | } |
| | | |
| | | /** æµç¨å®ä¹ */ |
| | |
| | | slots: { default: 'deploymentTime' }, |
| | | }, |
| | | { |
| | | field: 'autoApprove', |
| | | title: 'å
审æ¹', |
| | | minWidth: 110, |
| | | slots: { default: 'autoApprove' }, |
| | | }, |
| | | { |
| | | title: 'æä½', |
| | | width: 150, |
| | | fixed: 'right', |
| | |
| | | <script lang="ts" setup> |
| | | import type { ModelCategoryInfo } from '#/api/bpm/model'; |
| | | |
| | | import { onActivated, onMounted, reactive, ref, useTemplateRef, watch } from 'vue'; |
| | | import { |
| | | computed, |
| | | onActivated, |
| | | onMounted, |
| | | reactive, |
| | | ref, |
| | | useTemplateRef, |
| | | watch, |
| | | } from 'vue'; |
| | | |
| | | import { useAccess } from '@vben/access'; |
| | | import { Page, useVbenModal } from '@vben/common-ui'; |
| | | import { IconifyIcon } from '@vben/icons'; |
| | | import { cloneDeep } from '@vben/utils'; |
| | | |
| | | import { useSortable } from '@vueuse/integrations/useSortable'; |
| | | import { Button, Card, Dropdown, Input, Menu, message } from 'ant-design-vue'; |
| | | import { |
| | | Button, |
| | | Card, |
| | | Dropdown, |
| | | Input, |
| | | Menu, |
| | | message, |
| | | Switch, |
| | | Tooltip, |
| | | } from 'ant-design-vue'; |
| | | |
| | | import { |
| | | getAutoApproveList, |
| | | updateGlobalAutoApprove, |
| | | } from '#/api/bpm/autoApprove'; |
| | | import { |
| | | getCategorySimpleList, |
| | | updateCategorySortBatch, |
| | |
| | | }); |
| | | |
| | | const modelListSpinning = ref(false); // 模åå表å è½½ç¶æ |
| | | |
| | | /** å
¨å±å
å®¡æ¹æ»å¼å
³å ç¨çæµç¨ Keyï¼ä¸å端 BpmProcessAutoApproveService.GLOBAL_KEY ä¿æä¸è´ï¼ */ |
| | | const GLOBAL_AUTO_APPROVE_KEY = '*'; |
| | | const globalAutoApprove = ref(false); // å
¨å±å
å®¡æ¹æ»å¼å
³ |
| | | const globalAutoApproveLoading = ref(false); // æ»å¼å
³æäº¤ç¶æ |
| | | |
| | | const { hasAccessByCodes } = useAccess(); |
| | | /** å
审æ¹å¼å
³çæéï¼æ æéæ¶å¼å
³ç½®ç°ï¼é¿å
ç¹äºææ¥é */ |
| | | const hasPermiAutoApprove = computed(() => { |
| | | return hasAccessByCodes(['bpm:process-auto-approve:update']); |
| | | }); |
| | | |
| | | const saveSortLoading = ref(false); // ä¿åæåºç¶æ |
| | | const categoryGroup = ref<ModelCategoryInfo[]>([]); // æç
§ category åç»çæ°æ® |
| | |
| | | async function getList() { |
| | | modelListSpinning.value = true; |
| | | try { |
| | | const modelList = await getModelList(queryParams.name); |
| | | const categoryList = await getCategorySimpleList(); |
| | | const [modelList, categoryList, autoApproveList] = await Promise.all([ |
| | | getModelList(queryParams.name), |
| | | getCategorySimpleList(), |
| | | getAutoApproveList(), |
| | | ]); |
| | | // å
审æ¹é
ç½®æãæµç¨ Keyãå»ºç«æ å°ï¼åå¹¶å°æ¨¡åè¡æ°æ®ä¸ï¼ä¾å表å
çå
审æ¹å¼å
³åç´æ¥è¯»å |
| | | const autoApproveMap = new Map<string, boolean>(); |
| | | autoApproveList.forEach((config) => { |
| | | autoApproveMap.set(config.processDefinitionKey, config.autoApprove); |
| | | }); |
| | | modelList.forEach((model: any) => { |
| | | model.autoApprove = autoApproveMap.get(model.key) ?? false; |
| | | }); |
| | | // å
¨å±æ»å¼å
³ä»¥ç¹æ® Key åæ¾å¨å䏿¡é
ç½®é |
| | | globalAutoApprove.value = |
| | | autoApproveMap.get(GLOBAL_AUTO_APPROVE_KEY) ?? false; |
| | | // æç
§ category èå |
| | | categoryGroup.value = categoryList.map((category: any) => ({ |
| | | ...category, |
| | |
| | | sortableInstance.value.option('disabled', true); |
| | | } |
| | | } |
| | | |
| | | /** 忢å
¨å±å
å®¡æ¹æ»å¼å
³ï¼å¼å¯åæææµç¨ä¸å¾å
å®¡æ¹ */ |
| | | async function handleGlobalAutoApproveChange(checked: boolean) { |
| | | globalAutoApprove.value = checked; |
| | | globalAutoApproveLoading.value = true; |
| | | try { |
| | | await updateGlobalAutoApprove(checked); |
| | | message.success( |
| | | checked |
| | | ? 'å·²å¼å¯å
¨å±å
审æ¹ï¼æææµç¨æäº¤åå°ç´æ¥éè¿' |
| | | : 'å·²å
³éå
¨å±å
审æ¹ï¼æ¢å¤ä¸ºæåæµç¨å¼å
³å¤å®', |
| | | ); |
| | | // éæ°æåå表ï¼åæ¥åæµç¨å¼å
³çå±ç¤º |
| | | await getList(); |
| | | } catch (error) { |
| | | // å¤±è´¥åæ» |
| | | globalAutoApprove.value = !checked; |
| | | console.error('设置å
¨å±å
审æ¹å¼å
³å¤±è´¥', error); |
| | | } finally { |
| | | globalAutoApproveLoading.value = false; |
| | | } |
| | | } |
| | | </script> |
| | | |
| | | <template> |
| | |
| | | > |
| | | <template #extra> |
| | | <div v-if="!isCategorySorting"> |
| | | <Tooltip title="å¼å¯åæææµç¨ä¸å¾å
审æ¹ï¼ä¼å
äºåæµç¨èªå·±çå¼å
³ï¼"> |
| | | <span class="mr-1">å
¨é¨å
审æ¹</span> |
| | | </Tooltip> |
| | | <Switch |
| | | :checked="globalAutoApprove" |
| | | :loading="globalAutoApproveLoading" |
| | | :disabled="!hasPermiAutoApprove" |
| | | class="mr-3" |
| | | @change="(checked: any) => handleGlobalAutoApproveChange(checked)" |
| | | /> |
| | | <Input |
| | | v-model:value="queryParams.name" |
| | | placeholder="æç´¢æµç¨" |
| | |
| | | Dropdown, |
| | | Menu, |
| | | message, |
| | | Switch, |
| | | Tag, |
| | | Tooltip, |
| | | } from 'ant-design-vue'; |
| | | |
| | | import { useVbenVxeGrid } from '#/adapter/vxe-table'; |
| | | import { updateAutoApproveByKey } from '#/api/bpm/autoApprove'; |
| | | import { deleteCategory } from '#/api/bpm/category'; |
| | | import { |
| | | cleanModel, |
| | |
| | | const isModelSorting = ref(false); |
| | | const originalData = ref<BpmModelApi.Model[]>([]); |
| | | const modelList = ref<BpmModelApi.Model[]>([]); |
| | | const autoApproveLoadingId = ref<null | number>(null); // å
审æ¹å¼å
³ï¼æ£å¨æäº¤ç模åç¼å· |
| | | // TODO @jasonï¼å¯ä»¥å
¨é¨å±å¼ä¹ï¼ @èè¿ ä¸æ¬¡è®¨è®ºã好忝å 为æ§è½é®é¢æåªå±å¼ç¬¬ä¸ä¸ªåç±» |
| | | const isExpand = ref(props.isFirst); // æ ¹æ®æ¯å¦ä¸ºç¬¬ä¸ä¸ªåç±», æ¥è®¾ç½®åå§å±å¼ç¶æ |
| | | |
| | |
| | | }); |
| | | const hasPermiDeploy = computed(() => { |
| | | return hasAccessByCodes(['bpm:model:deploy']); |
| | | }); |
| | | /** å
审æ¹å¼å
³çæéï¼æ æéæ¶å¼å
³ç½®ç°ï¼é¿å
ç¹äºææ¥é */ |
| | | const hasPermiAutoApprove = computed(() => { |
| | | return hasAccessByCodes(['bpm:process-auto-approve:update']); |
| | | }); |
| | | |
| | | const [Grid, gridApi] = useVbenVxeGrid({ |
| | |
| | | message.success(`åå¸[${row.name}]æµç¨æå`); |
| | | // å·æ°å表 |
| | | emit('success'); |
| | | } |
| | | |
| | | /** 忢å
审æ¹å¼å
³ï¼å¼å¯å该æµç¨æäº¤æ¶ä¸åè¿å
¥äººå·¥å®¡æ¹ï¼ç±ç³»ç»èªå¨éè¿ */ |
| | | async function handleAutoApproveChange(checked: boolean, row: any) { |
| | | const oldValue = row.autoApprove; |
| | | // ä¹è§æ´æ°ï¼é¿å
çå¾
æ¥å£æé´å¼å
³ç¶æåè·³ |
| | | row.autoApprove = checked; |
| | | gridApi.setGridOptions({ data: modelList.value }); |
| | | autoApproveLoadingId.value = row.id; |
| | | try { |
| | | await updateAutoApproveByKey(row.key, checked); |
| | | message.success( |
| | | checked |
| | | ? `å·²å¼å¯[${row.name}]å
审æ¹ï¼è¯¥æµç¨æäº¤åå°ç±ç³»ç»èªå¨éè¿` |
| | | : `å·²å
³é[${row.name}]å
审æ¹ï¼æ¢å¤ä¸ºæ£å¸¸äººå·¥å®¡æ¹`, |
| | | ); |
| | | // éæ°æåå表ï¼ä¿è¯åç«¯ç¶æä¸å端ä¸è´ |
| | | emit('success'); |
| | | } catch (error) { |
| | | // å¤±è´¥åæ» |
| | | row.autoApprove = oldValue; |
| | | gridApi.setGridOptions({ data: modelList.value }); |
| | | console.error('设置å
审æ¹å¼å
³å¤±è´¥', error); |
| | | } finally { |
| | | autoApproveLoadingId.value = null; |
| | | } |
| | | } |
| | | |
| | | /** 'æ´å¤'æä½æé® */ |
| | |
| | | </Tag> |
| | | </div> |
| | | </template> |
| | | <template #autoApprove="{ row }"> |
| | | <div class="flex items-center gap-1"> |
| | | <Switch |
| | | :checked="row.autoApprove" |
| | | :loading="autoApproveLoadingId === row.id" |
| | | :disabled="!hasPermiAutoApprove" |
| | | size="small" |
| | | @change=" |
| | | (checked: any) => handleAutoApproveChange(checked, row) |
| | | " |
| | | /> |
| | | <Tooltip |
| | | v-if="!row.processDefinition" |
| | | title="æµç¨å°æªåå¸ï¼å
审æ¹å¼å
³ä¸ä¼çæï¼è¯·å
å叿µç¨" |
| | | > |
| | | <IconifyIcon |
| | | icon="lucide:alert-circle" |
| | | class="text-base text-orange-400" |
| | | /> |
| | | </Tooltip> |
| | | </div> |
| | | </template> |
| | | <template #actions="{ row }"> |
| | | <div class="flex items-center space-x-0"> |
| | | <Button |