feat: 增加流程作废功能并优化按钮组件

This commit is contained in:
AN
2025-05-25 09:10:49 +08:00
parent f9d57f1b71
commit 350de08f8f
4 changed files with 84 additions and 111 deletions

View File

@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, useAttrs } from 'vue'; import { type VNode, computed, useAttrs } from 'vue';
import type { ButtonProps, PopoverPlacement } from 'naive-ui'; import type { ButtonProps, PopoverPlacement } from 'naive-ui';
import { twMerge } from 'tailwind-merge'; import { twMerge } from 'tailwind-merge';
@ -11,6 +11,8 @@ defineOptions({
interface Props { interface Props {
/** Button class */ /** Button class */
class?: string; class?: string;
/** Show popconfirm icon */
showPopconfirmIcon?: boolean;
/** Iconify icon name */ /** Iconify icon name */
icon?: string; icon?: string;
/** Local icon name */ /** Local icon name */
@ -19,8 +21,8 @@ interface Props {
tooltipContent?: string; tooltipContent?: string;
/** Tooltip placement */ /** Tooltip placement */
tooltipPlacement?: PopoverPlacement; tooltipPlacement?: PopoverPlacement;
/** Popconfirm content */ /** Popconfirm content - can be string or VNode */
popconfirmContent?: string; popconfirmContent?: string | VNode;
zIndex?: number; zIndex?: number;
quaternary?: boolean; quaternary?: boolean;
[key: string]: any; [key: string]: any;
@ -28,6 +30,7 @@ interface Props {
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
class: '', class: '',
showPopconfirmIcon: true,
icon: '', icon: '',
localIcon: '', localIcon: '',
tooltipContent: '', tooltipContent: '',
@ -59,8 +62,11 @@ const handlePositiveClick = () => {
<template> <template>
<NTooltip :placement="tooltipPlacement" :z-index="zIndex" :disabled="!tooltipContent"> <NTooltip :placement="tooltipPlacement" :z-index="zIndex" :disabled="!tooltipContent">
<template #trigger> <template #trigger>
<NPopconfirm :disabled="!popconfirmContent" @positive-click="handlePositiveClick"> <NPopconfirm :show-icon="showPopconfirmIcon" :disabled="!popconfirmContent" @positive-click="handlePositiveClick">
{{ popconfirmContent }} <template #default>
<component :is="popconfirmContent" v-if="typeof popconfirmContent !== 'string'" />
<template v-else>{{ popconfirmContent }}</template>
</template>
<template #trigger> <template #trigger>
<NButton <NButton
:quaternary="quaternary" :quaternary="quaternary"

View File

@ -9,14 +9,6 @@ export function fetchGetRunningProcessInstanceList(params: Api.Workflow.ProcessI
}); });
} }
/** 查询正在运行的流程实例列表 */
export function fetchGetFinishProcessInstanceList(params: Api.Workflow.ProcessInstanceSearchParams) {
return request<Api.Workflow.ProcessInstanceList>({
url: '/workflow/instance/pageByFinish',
method: 'get',
params
});
}
/** 查询已结束的流程实例列表 */ /** 查询已结束的流程实例列表 */
export function fetchGetFinishedProcessInstanceList(params: Api.Workflow.ProcessInstanceSearchParams) { export function fetchGetFinishedProcessInstanceList(params: Api.Workflow.ProcessInstanceSearchParams) {
return request<Api.Workflow.ProcessInstanceList>({ return request<Api.Workflow.ProcessInstanceList>({
@ -29,7 +21,16 @@ export function fetchGetFinishedProcessInstanceList(params: Api.Workflow.Process
/** 按照实例id删除流程实例 */ /** 按照实例id删除流程实例 */
export function fetchBatchDeleteProcessInstance(instanceIds: CommonType.IdType[]) { export function fetchBatchDeleteProcessInstance(instanceIds: CommonType.IdType[]) {
return request<boolean>({ return request<boolean>({
url: `/workflow/deleteByInstanceIds/${instanceIds.join(',')}`, url: `/workflow/instance/deleteByInstanceIds/${instanceIds.join(',')}`,
method: 'delete' method: 'delete'
}); });
} }
/** 流程作废操作 */
export function fetchFlowInvalidOperate(data: Api.Workflow.FlowInvalidOperateParams) {
return request<boolean>({
url: '/workflow/instance/invalid',
method: 'post',
data
});
}

View File

@ -156,5 +156,13 @@ declare namespace Api {
>; >;
/** 流程实例列表 */ /** 流程实例列表 */
type ProcessInstanceList = Common.PaginatingQueryRecord<ProcessInstance>; type ProcessInstanceList = Common.PaginatingQueryRecord<ProcessInstance>;
/** 流程作废操作参数 */
type FlowInvalidOperateParams = CommonType.RecordNullable<{
/** 主键 */
id: CommonType.IdType;
/** 作废原因 */
comment: string;
}>;
} }
} }

View File

@ -1,11 +1,12 @@
<script setup lang="tsx"> <script setup lang="tsx">
import { computed, ref, watch } from 'vue'; import { computed, reactive, ref, watch } from 'vue';
import { NButton, NDivider, NEmpty, NInput, NRadioButton, NRadioGroup, NSpin, NTag } from 'naive-ui'; import { NButton, NDivider, NEmpty, NInput, NRadioButton, NRadioGroup, NSpin, NTag } from 'naive-ui';
import { workflowActivityStatusRecord } from '@/constants/workflow'; import { workflowActivityStatusRecord } from '@/constants/workflow';
import { fetchGetCategoryTree } from '@/service/api/workflow/category'; import { fetchGetCategoryTree } from '@/service/api/workflow/category';
import { import {
fetchBatchDeleteProcessInstance, fetchBatchDeleteProcessInstance,
fetchGetFinishProcessInstanceList, fetchFlowInvalidOperate,
fetchGetFinishedProcessInstanceList,
fetchGetRunningProcessInstanceList fetchGetRunningProcessInstanceList
} from '@/service/api/workflow/instance'; } from '@/service/api/workflow/instance';
import { useAppStore } from '@/store/modules/app'; import { useAppStore } from '@/store/modules/app';
@ -41,6 +42,17 @@ const runningStatusOptions = ref<RunningStatusOption[]>([
} }
]); ]);
type CancelModel = Api.Workflow.FlowInvalidOperateParams;
const cancelModel: CancelModel = reactive(createDefaultModel());
function createDefaultModel(): CancelModel {
return {
id: null,
comment: ''
};
}
// 基础列 // 基础列
const baseColumns = ref<NaiveUI.TableColumn<Api.Workflow.ProcessInstance>[]>([ const baseColumns = ref<NaiveUI.TableColumn<Api.Workflow.ProcessInstance>[]>([
{ {
@ -50,7 +62,7 @@ const baseColumns = ref<NaiveUI.TableColumn<Api.Workflow.ProcessInstance>[]>([
}, },
{ {
key: 'flowName', key: 'flowName',
title: '流程定义名称', title: '流程名称',
align: 'center', align: 'center',
width: 120 width: 120
}, },
@ -62,7 +74,7 @@ const baseColumns = ref<NaiveUI.TableColumn<Api.Workflow.ProcessInstance>[]>([
}, },
{ {
key: 'flowCode', key: 'flowCode',
title: '流程定义编码', title: '流程编码',
align: 'center', align: 'center',
minWidth: 120 minWidth: 120
}, },
@ -70,7 +82,10 @@ const baseColumns = ref<NaiveUI.TableColumn<Api.Workflow.ProcessInstance>[]>([
key: 'categoryName', key: 'categoryName',
title: '流程分类', title: '流程分类',
align: 'center', align: 'center',
minWidth: 120 minWidth: 120,
render(row) {
return <NTag type="default">{row.categoryName}</NTag>;
}
}, },
{ {
key: 'createByName', key: 'createByName',
@ -134,30 +149,34 @@ const operateColumns = ref<NaiveUI.TableColumn<Api.Workflow.ProcessInstance>[]>(
width: 150, width: 150,
render: row => { render: row => {
const showAll = runningStatus.value; const showAll = runningStatus.value;
const id = row.id;
return ( return (
<div class="flex-center gap-1px"> <div class="flex-center gap-1px">
{showAll && ( {showAll && [
<> <ButtonIcon
<ButtonIcon key="cancel"
text text
type="error" type="error"
icon="material-symbols:cancel-outline-rounded" showPopconfirmIcon={false}
tooltipContent="作废流程" icon="material-symbols:cancel-outline-rounded"
onClick={() => edit(row.id!)} tooltipContent="作废流程"
/> popconfirmContent={
<NDivider vertical /> <NInput v-model:value={cancelModel.comment} size="large" type="textarea" placeholder="请输入作废原因" />
<ButtonIcon }
text onPositiveClick={() => handleCancel(id)}
type="error" />,
icon="material-symbols:delete-outline" <NDivider key="div1" vertical />,
tooltipContent={$t('common.delete')} <ButtonIcon
popconfirmContent={$t('common.confirmDelete')} key="delete"
onPositiveClick={() => handleDelete(row.id!)} text
/> type="error"
<NDivider vertical /> icon="material-symbols:delete-outline"
</> tooltipContent={$t('common.delete')}
)} popconfirmContent={$t('common.confirmDelete')}
onPositiveClick={() => handleDelete(id)}
/>,
<NDivider key="div2" vertical />
]}
<ButtonIcon text type="info" icon="material-symbols:visibility-outline" tooltipContent="流程预览" /> <ButtonIcon text type="info" icon="material-symbols:visibility-outline" tooltipContent="流程预览" />
<NDivider vertical /> <NDivider vertical />
<ButtonIcon text type="info" icon="material-symbols:variable-insert" tooltipContent="流程变量" /> <ButtonIcon text type="info" icon="material-symbols:variable-insert" tooltipContent="流程变量" />
@ -196,10 +215,10 @@ const {
: [...baseColumns.value, ...finishColumns.value, ...operateColumns.value] : [...baseColumns.value, ...finishColumns.value, ...operateColumns.value]
}); });
const { handleEdit, checkedRowKeys, onBatchDeleted, onDeleted } = useTableOperate(data, getData); const { checkedRowKeys, onBatchDeleted, onDeleted } = useTableOperate(data, getData);
// 监听运行状态变化 // 监听运行状态变化
watch(runningStatus, async () => { watch(runningStatus, async () => {
const newApiFn = runningStatus.value ? fetchGetRunningProcessInstanceList : fetchGetFinishProcessInstanceList; const newApiFn = runningStatus.value ? fetchGetRunningProcessInstanceList : fetchGetFinishedProcessInstanceList;
updateApiFn(newApiFn); updateApiFn(newApiFn);
reloadColumns(); reloadColumns();
await getDataByPage(); await getDataByPage();
@ -256,8 +275,12 @@ async function handleDelete(instanceId: CommonType.IdType) {
onDeleted(); onDeleted();
} }
async function edit(instanceId: CommonType.IdType) { async function handleCancel(instanceId: CommonType.IdType) {
handleEdit('id', instanceId); cancelModel.id = instanceId;
// request
const { error } = await fetchFlowInvalidOperate(cancelModel);
if (error) return;
getDataByPage();
} }
</script> </script>
@ -329,7 +352,7 @@ async function edit(instanceId: CommonType.IdType) {
:data="data" :data="data"
size="small" size="small"
:flex-height="!appStore.isMobile" :flex-height="!appStore.isMobile"
:scroll-x="1078" :scroll-x="1400"
:loading="loading" :loading="loading"
remote remote
:row-key="row => row.id" :row-key="row => row.id"
@ -340,68 +363,3 @@ async function edit(instanceId: CommonType.IdType) {
</div> </div>
</TableSiderLayout> </TableSiderLayout>
</template> </template>
<style scoped lang="scss">
.category-tree {
.n-button {
--n-padding: 8px !important;
}
:deep(.n-tree__empty) {
height: 100%;
justify-content: center;
}
:deep(.n-spin-content) {
height: 100%;
}
:deep(.infinite-scroll) {
height: calc(100vh - 228px - var(--calc-footer-height, 0px)) !important;
max-height: calc(100vh - 228px - var(--calc-footer-height, 0px)) !important;
}
@media screen and (max-width: 1024px) {
:deep(.infinite-scroll) {
height: calc(100vh - 227px - var(--calc-footer-height, 0px)) !important;
max-height: calc(100vh - 227px - var(--calc-footer-height, 0px)) !important;
}
}
:deep(.n-tree-node) {
height: 25px;
}
:deep(.n-tree-node-switcher) {
height: 25px;
}
:deep(.n-tree-node-switcher__icon) {
font-size: 16px !important;
height: 16px !important;
width: 16px !important;
}
}
:deep(.n-data-table-wrapper),
:deep(.n-data-table-base-table),
:deep(.n-data-table-base-table-body) {
height: 100%;
}
@media screen and (max-width: 800px) {
:deep(.n-data-table-base-table-body) {
max-height: calc(100vh - 400px - var(--calc-footer-height, 0px));
}
}
@media screen and (max-width: 802px) {
:deep(.n-data-table-base-table-body) {
max-height: calc(100vh - 473px - var(--calc-footer-height, 0px));
}
}
:deep(.n-card-header__main) {
min-width: 69px !important;
}
</style>