feat(projects): 新增加签功能

This commit is contained in:
AN 2025-06-27 18:22:21 +08:00
parent 188533adc9
commit 55dceca28b
9 changed files with 105 additions and 68 deletions

View File

@ -251,7 +251,7 @@ watch(visible, () => {
:title="$t('page.system.user.title')" :title="$t('page.system.user.title')"
:bordered="false" :bordered="false"
size="small" size="small"
class="sm:flex-1-hidden card-wrapper lt-sm:overflow-auto" class="card-wrapper sm:flex-1-hidden lt-sm:overflow-auto"
> >
<template #header-extra> <template #header-extra>
<TableHeaderOperation <TableHeaderOperation

View File

@ -128,12 +128,7 @@ async function initData() {
activeTab.value = 'image'; activeTab.value = 'image';
instanceId.value = undefined; instanceId.value = undefined;
hisTask.value = []; hisTask.value = [];
startLoading(); await getData();
try {
await getData();
} finally {
endLoading();
}
} }
async function getData() { async function getData() {

View File

@ -1,16 +1,19 @@
<script lang="ts" setup> <script lang="ts" setup>
import { computed, reactive, watch } from 'vue'; import { computed, reactive, ref, watch } from 'vue';
import { useBoolean } from '@sa/hooks'; import { useBoolean, useLoading } from '@sa/hooks';
import { fetchTaskOperate, fetchTerminateTask } from '@/service/api/workflow/task'; import { fetchGetTask, fetchTaskOperate, fetchTerminateTask } from '@/service/api/workflow/task';
defineOptions({ defineOptions({
name: 'FlowInterveneModal' name: 'FlowInterveneModal'
}); });
const { bool: multiInstanceVisible, setTrue: openMultiInstanceModal } = useBoolean(); const { loading, startLoading, endLoading } = useLoading();
const { bool: addSignatureVisible, setTrue: openAddSignatureModal } = useBoolean();
const { bool: transferVisible, setTrue: openTransferModal } = useBoolean(); const { bool: transferVisible, setTrue: openTransferModal } = useBoolean();
interface Props { interface Props {
rowData: Api.Workflow.Task; taskId: CommonType.IdType;
assigneeIds: CommonType.IdType[];
assigneeNames: string[];
} }
const props = defineProps<Props>(); const props = defineProps<Props>();
@ -21,24 +24,25 @@ interface Emits {
const emit = defineEmits<Emits>(); const emit = defineEmits<Emits>();
const isWaiting = computed(() => props.rowData.flowStatus === 'waiting'); const taskInfo = ref<Api.Workflow.Task>();
const isWaiting = computed(() => taskInfo.value?.flowStatus === 'waiting');
// 0 // 0
const isTicketOrSignInstance = computed(() => Number(props.rowData.nodeRatio) > 0); const isTicketOrSignInstance = computed(() => Number(taskInfo.value?.nodeRatio) > 0);
const visible = defineModel<boolean>('visible', { const visible = defineModel<boolean>('visible', {
default: false default: false
}); });
type Model = Api.Workflow.TaskOperateParams; type Model = Api.Workflow.TaskOperateParams;
const model: Model = reactive(createDefaultModel()); const model: Model = reactive(createDefaultModel());
function createDefaultModel(): Model { function createDefaultModel(): Model {
return { return {
taskId: null, taskId: null,
userId: null, userId: undefined,
userIds: null, userIds: undefined,
message: '' message: ''
}; };
} }
@ -77,6 +81,30 @@ function handleTransferConfirm(ids: CommonType.IdType[]) {
}); });
} }
function handleAddSignatureConfirm(ids: CommonType.IdType[]) {
if (ids.length === 0) {
window.$message?.error('请选择加签用户');
return;
}
model.userIds = ids;
window.$dialog?.warning({
title: '提示',
content: '是否确认加签?',
positiveText: '确认加签',
positiveButtonProps: {
type: 'primary'
},
negativeText: '取消',
onPositiveClick: async () => {
const { error } = await fetchTaskOperate(model, 'addSignature');
if (error) return;
window.$message?.success('加签成功');
visible.value = false;
emit('refresh');
}
});
}
function handleTerminate() { function handleTerminate() {
window.$dialog?.warning({ window.$dialog?.warning({
title: '提示', title: '提示',
@ -96,62 +124,72 @@ function handleTerminate() {
}); });
} }
async function getTaskInfo() {
startLoading();
const { error, data } = await fetchGetTask(props.taskId);
if (error) return;
taskInfo.value = data;
endLoading();
}
watch(visible, () => { watch(visible, () => {
if (visible.value) { if (visible.value) {
Object.assign(model, createDefaultModel()); getTaskInfo();
model.taskId = props.rowData.id;
Object.assign(terminateModel, createDefaultTerminateModel());
terminateModel.taskId = props.rowData.id;
} }
}); });
</script> </script>
<template> <template>
<NModal v-model:show="visible" class="max-h-520px max-w-90% w-700px" title="流程干预" preset="card" size="medium"> <NModal
<NDescriptions :title="props.rowData.flowName" label-placement="left" :column="2" size="small" bordered> v-model:show="visible"
<NDescriptionsItem label="任务名称"> class="max-h-520px max-w-90% w-700px"
{{ props.rowData.nodeName }} title="流程干预"
</NDescriptionsItem> preset="card"
<NDescriptionsItem label="节点编码"> size="medium"
{{ props.rowData.nodeCode }} :native-scrollbar="false"
</NDescriptionsItem> >
<NDescriptionsItem label="开始时间"> <NSpin :show="loading">
{{ props.rowData.createTime }} <NDescriptions :title="taskInfo?.flowName" label-placement="left" :column="2" size="small" bordered>
</NDescriptionsItem> <NDescriptionsItem label="任务名称">
<NDescriptionsItem label="流程实例ID"> {{ taskInfo?.nodeName }}
{{ props.rowData.instanceId }} </NDescriptionsItem>
</NDescriptionsItem> <NDescriptionsItem label="节点编码">
<NDescriptionsItem label="办理人"> {{ taskInfo?.nodeCode }}
<GroupTag :value="props.rowData.assigneeNames" /> </NDescriptionsItem>
</NDescriptionsItem> <NDescriptionsItem label="开始时间">
<NDescriptionsItem label="版本号"> {{ taskInfo?.createTime }}
<NTag type="info" size="small">v{{ props.rowData.version }}.0</NTag> </NDescriptionsItem>
</NDescriptionsItem> <NDescriptionsItem label="流程实例ID">
<NDescriptionsItem label="业务ID"> {{ taskInfo?.instanceId }}
{{ props.rowData.businessId }} </NDescriptionsItem>
</NDescriptionsItem> <NDescriptionsItem label="办理人">
</NDescriptions> <GroupTag :value="assigneeNames" />
</NDescriptionsItem>
<NDescriptionsItem label="版本号">
<NTag type="info" size="small">v{{ taskInfo?.version }}.0</NTag>
</NDescriptionsItem>
<NDescriptionsItem label="业务ID">
{{ taskInfo?.businessId }}
</NDescriptionsItem>
</NDescriptions>
</NSpin>
<template #footer> <template #footer>
<NSpace justify="end" :size="16"> <NSpace justify="end" :size="16">
<NButton v-if="isWaiting" type="primary" @click="openTransferModal">转办</NButton> <NButton v-if="isWaiting" type="primary" @click="openTransferModal">转办</NButton>
<NButton v-if="isWaiting && isTicketOrSignInstance" type="primary" @click="openMultiInstanceModal"> <NButton v-if="isWaiting && isTicketOrSignInstance" type="primary" @click="openAddSignatureModal">加签</NButton>
加签
</NButton>
<NButton v-if="isWaiting && isTicketOrSignInstance" type="primary">减签</NButton> <NButton v-if="isWaiting && isTicketOrSignInstance" type="primary">减签</NButton>
<NButton v-if="isWaiting" type="error" @click="handleTerminate">中止</NButton> <NButton v-if="isWaiting" type="error" @click="handleTerminate">中止</NButton>
</NSpace> </NSpace>
</template> </template>
<!-- 转办用户选择器 --> <!-- 转办用户选择器 -->
<UserSelectModal <UserSelectModal v-model:visible="transferVisible" :disabled-ids="assigneeIds" @confirm="handleTransferConfirm" />
v-model:visible="transferVisible"
:disabled-ids="props.rowData.assigneeIds.split(',')"
@confirm="handleTransferConfirm"
/>
<!-- 加签用户选择器 --> <!-- 加签用户选择器 -->
<UserSelectModal <UserSelectModal
v-model:visible="multiInstanceVisible" v-model:visible="addSignatureVisible"
multiple multiple
:disabled-ids="props.rowData.assigneeIds.split(',')" :disabled-ids="assigneeIds"
@confirm="handleAddSignatureConfirm"
/> />
</NModal> </NModal>
</template> </template>

View File

@ -25,6 +25,6 @@ const iframeUrl = `${baseURL}/warm-flow-ui/index.html?${stringify(urlParams)}`;
<template> <template>
<div> <div>
<iframe :src="iframeUrl" class="h-[400px] w-full" /> <iframe :src="iframeUrl" class="h-[600px] w-full" />
</div> </div>
</template> </template>

View File

@ -155,7 +155,7 @@ function handleExport() {
<template> <template>
<div class="min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto"> <div class="min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto">
<WorkflowCategorySearch v-model:model="searchParams" @reset="resetSearchParams" @search="getData" /> <WorkflowCategorySearch v-model:model="searchParams" @reset="resetSearchParams" @search="getData" />
<NCard title="流程分类列表" :bordered="false" size="small" class="sm:flex-1-hidden card-wrapper"> <NCard title="流程分类列表" :bordered="false" size="small" class="card-wrapper sm:flex-1-hidden">
<template #header-extra> <template #header-extra>
<TableHeaderOperation <TableHeaderOperation
v-model:columns="columnChecks" v-model:columns="columnChecks"

View File

@ -226,7 +226,7 @@ function handleExport() {
<template> <template>
<div class="min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto"> <div class="min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto">
<LeaveSearch v-model:model="searchParams" @reset="resetSearchParams" @search="getDataByPage" /> <LeaveSearch v-model:model="searchParams" @reset="resetSearchParams" @search="getDataByPage" />
<NCard title="请假申请列表" :bordered="false" size="small" class="sm:flex-1-hidden card-wrapper"> <NCard title="请假申请列表" :bordered="false" size="small" class="card-wrapper sm:flex-1-hidden">
<template #header-extra> <template #header-extra>
<TableHeaderOperation <TableHeaderOperation
v-model:columns="columnChecks" v-model:columns="columnChecks"

View File

@ -410,7 +410,7 @@ const selectable = computed(() => {
</template> </template>
<div class="h-full flex-col-stretch gap-12px overflow-hidden lt-sm:overflow-auto"> <div class="h-full flex-col-stretch gap-12px overflow-hidden lt-sm:overflow-auto">
<DefinitionSearch v-model:model="searchParams" @reset="resetSearchParams" @search="getDataByPage" /> <DefinitionSearch v-model:model="searchParams" @reset="resetSearchParams" @search="getDataByPage" />
<NCard :bordered="false" size="small" class="sm:flex-1-hidden card-wrapper"> <NCard :bordered="false" size="small" class="card-wrapper sm:flex-1-hidden">
<template #header> <template #header>
<NSpace> <NSpace>
<NRadioGroup v-model:value="isPublish" on-up size="small"> <NRadioGroup v-model:value="isPublish" on-up size="small">

View File

@ -365,7 +365,7 @@ async function handlePreview(row: Api.Workflow.ProcessInstance) {
</template> </template>
<div class="h-full flex-col-stretch gap-12px overflow-hidden lt-sm:overflow-auto"> <div class="h-full flex-col-stretch gap-12px overflow-hidden lt-sm:overflow-auto">
<ProcessInstanceSearch v-model:model="searchParams" @reset="handleResetSearch" @search="getDataByPage" /> <ProcessInstanceSearch v-model:model="searchParams" @reset="handleResetSearch" @search="getDataByPage" />
<NCard :bordered="false" size="small" class="sm:flex-1-hidden card-wrapper"> <NCard :bordered="false" size="small" class="card-wrapper sm:flex-1-hidden">
<template #header> <template #header>
<NSpace> <NSpace>
<NRadioGroup v-model:value="runningStatus" on-up size="small"> <NRadioGroup v-model:value="runningStatus" on-up size="small">

View File

@ -30,8 +30,6 @@ const { bool: viewVisible, setTrue: showViewDrawer } = useBoolean();
const { bool: interveneVisible, setTrue: showInterveneDrawer } = useBoolean(); const { bool: interveneVisible, setTrue: showInterveneDrawer } = useBoolean();
const dynamicComponent = shallowRef(); const dynamicComponent = shallowRef();
type Task = Api.Workflow.Task;
const waitingStatus = ref<boolean>(true); const waitingStatus = ref<boolean>(true);
const waitingStatusOptions = ref<WaitingStatusOption[]>([ const waitingStatusOptions = ref<WaitingStatusOption[]>([
{ label: '待办任务', value: true }, { label: '待办任务', value: true },
@ -123,7 +121,7 @@ const operateColumns = ref<NaiveUI.TableColumn<Api.Workflow.TaskOrHisTask>[]>([
type="info" type="info"
icon="material-symbols:edit-document" icon="material-symbols:edit-document"
tooltipContent="流程干预" tooltipContent="流程干预"
onClick={() => handleIntervene(row)} onClick={() => handleIntervene(row as Api.Workflow.Task)}
/> />
); );
} }
@ -222,9 +220,13 @@ async function handleView(row: Api.Workflow.TaskOrHisTask) {
} }
} }
const interveneRowData = ref<Api.Workflow.TaskOrHisTask>(); const taskId = ref<CommonType.IdType>('');
function handleIntervene(row: Api.Workflow.TaskOrHisTask) { const assigneeIds = ref<CommonType.IdType[]>([]);
interveneRowData.value = row; const assigneeNames = ref<string[]>([]);
function handleIntervene(row: Api.Workflow.Task) {
taskId.value = row.id;
assigneeIds.value = row.assigneeIds?.split(',') || [];
assigneeNames.value = row.assigneeNames?.split(',') || [];
showInterveneDrawer(); showInterveneDrawer();
} }
</script> </script>
@ -264,7 +266,7 @@ function handleIntervene(row: Api.Workflow.TaskOrHisTask) {
</template> </template>
<div class="h-full flex-col-stretch gap-12px overflow-hidden lt-sm:overflow-auto"> <div class="h-full flex-col-stretch gap-12px overflow-hidden lt-sm:overflow-auto">
<AllTaskWaitingSearch v-model:model="searchParams" @reset="handleResetSearch" @search="getDataByPage" /> <AllTaskWaitingSearch v-model:model="searchParams" @reset="handleResetSearch" @search="getDataByPage" />
<NCard :bordered="false" size="small" class="sm:flex-1-hidden card-wrapper"> <NCard :bordered="false" size="small" class="card-wrapper sm:flex-1-hidden">
<template #header> <template #header>
<NSpace> <NSpace>
<NRadioGroup v-model:value="waitingStatus" on-up size="small"> <NRadioGroup v-model:value="waitingStatus" on-up size="small">
@ -306,7 +308,9 @@ function handleIntervene(row: Api.Workflow.TaskOrHisTask) {
<component :is="dynamicComponent" :visible="viewVisible" operate-type="detail" :business-id="businessId" /> <component :is="dynamicComponent" :visible="viewVisible" operate-type="detail" :business-id="businessId" />
<FlowInterveneModal <FlowInterveneModal
v-model:visible="interveneVisible" v-model:visible="interveneVisible"
:row-data="interveneRowData as Task" :task-id="taskId"
:assignee-ids="assigneeIds"
:assignee-names="assigneeNames"
@refresh="getData" @refresh="getData"
/> />
</NCard> </NCard>