feat(projects): 添加修改办理人,催办功能,优化组件样式

This commit is contained in:
AN
2025-09-05 13:28:47 +08:00
parent b07729edd1
commit bc44d2cddc
9 changed files with 187 additions and 9 deletions

View File

@ -260,7 +260,7 @@ watch(visible, () => {
size="medium"
:title="props.title"
>
<TableSiderLayout :sider-title="$t('page.system.dept.title')">
<TableSiderLayout class="bg-gray-50 p-2" :sider-title="$t('page.system.dept.title')">
<template #header-extra>
<NButton size="small" text class="h-18px" @click.stop="() => handleResetTreeData()">
<template #icon>

View File

@ -165,7 +165,7 @@ defineExpose({
<template>
<NDivider />
<div>
<div class="h-full">
<NTabs v-model:value="activeTab" type="segment" animated>
<NTabPane display-directive="show" bar-width="100px" name="image" tab="流程图">
<FlowPreview v-if="instanceId" :instance-id="instanceId" />

View File

@ -177,12 +177,24 @@ watch(visible, () => {
<template #footer>
<NSpace justify="end" :size="16">
<NButton v-if="isWaiting" type="primary" @click="openTransferModal">转办</NButton>
<NButton v-if="isWaiting && isTicketOrSignInstance" type="primary" @click="openAddSignatureModal">加签</NButton>
<NButton v-if="isWaiting && isTicketOrSignInstance" type="primary" @click="openReduceSignatureModal">
<NButton v-if="isWaiting" :loading="loading" type="primary" @click="openTransferModal">转办</NButton>
<NButton
v-if="isWaiting && isTicketOrSignInstance"
:loading="loading"
type="primary"
@click="openAddSignatureModal"
>
加签
</NButton>
<NButton
v-if="isWaiting && isTicketOrSignInstance"
:loading="loading"
type="primary"
@click="openReduceSignatureModal"
>
减签
</NButton>
<NButton v-if="isWaiting" type="error" @click="handleTerminate">终止</NButton>
<NButton v-if="isWaiting" :loading="loading" type="error" @click="handleTerminate">终止</NButton>
</NSpace>
</template>
<!-- 转办用户选择器 -->

View File

@ -133,7 +133,7 @@ async function getTask() {
return;
}
task.value = data;
task.value.buttonList.forEach(item => {
task.value.buttonList?.forEach(item => {
buttonPerm[item.code as keyof ButtonPerm] = item.show!;
});
endBtnLoading();

View File

@ -0,0 +1,95 @@
<script lang="ts" setup>
import { reactive, watch } from 'vue';
import { messageTypeOptions } from '@/constants/workflow';
import { fetchTaskUrge } from '@/service/api/workflow';
import { useFormRules, useNaiveForm } from '@/hooks/common/form';
defineOptions({
name: 'FlowUrgeModal'
});
const { createRequiredRule } = useFormRules();
const { formRef, validate } = useNaiveForm();
interface Props {
taskIds: CommonType.IdType[];
}
const props = defineProps<Props>();
interface Emits {
(e: 'submit'): void;
}
const emit = defineEmits<Emits>();
const visible = defineModel<boolean>('visible', {
default: false
});
type Model = Api.Workflow.TaskUrgeOperateParams;
const model = reactive(createDefaultModel());
function createDefaultModel(): Model {
return {
taskIdList: props.taskIds,
messageType: ['1'],
message: ''
};
}
type RuleKey = Extract<keyof Model, 'taskIdList' | 'messageType' | 'message'>;
const rules: Record<RuleKey, App.Global.FormRule> = {
taskIdList: createRequiredRule('任务ID不能为空'),
messageType: createRequiredRule('消息提醒不能为空'),
message: createRequiredRule('消息内容不能为空')
};
function closeDrawer() {
visible.value = false;
}
watch(visible, () => {
if (visible.value) {
Object.assign(model, createDefaultModel());
}
});
async function handleSubmit() {
await validate();
const { error } = await fetchTaskUrge(model);
if (error) return;
window.$message?.success('催办成功');
closeDrawer();
emit('submit');
}
</script>
<template>
<NModal v-model:show="visible" preset="card" class="w-800px" title="催办" :native-scrollbar="false" closable>
<NForm ref="formRef" :model="model" :rules="rules">
<NFormItem label="消息提醒" path="messageType">
<NCheckboxGroup v-model:value="model.messageType">
<NSpace item-style="display: flex;">
<NCheckbox
v-for="item in messageTypeOptions"
:key="item.value"
:disabled="item.value === '1'"
:value="item.value"
:label="item.label"
/>
</NSpace>
</NCheckboxGroup>
</NFormItem>
<NFormItem label="消息呢用" path="message">
<NInput v-model:value="model.message" type="textarea" />
</NFormItem>
</NForm>
<template #footer>
<NSpace justify="end" :size="16">
<NButton @click="closeDrawer">{{ $t('common.cancel') }}</NButton>
<NButton type="primary" @click="handleSubmit">{{ $t('common.confirm') }}</NButton>
</NSpace>
</template>
</NModal>
</template>

View File

@ -122,3 +122,21 @@ export function fetchGetCopyTask(data: Api.Workflow.TaskSearchParams) {
params: data
});
}
/** 修改办理人 */
export function fetchTaskAssignee(taskIds: CommonType.IdType[], userId: CommonType.IdType) {
return request<boolean>({
url: `/workflow/task/updateAssignee/${userId}`,
method: 'put',
data: taskIds
});
}
/** 任务催办 */
export function fetchTaskUrge(data: Api.Workflow.TaskUrgeOperateParams) {
return request<boolean>({
url: '/workflow/task/urgeTask',
method: 'post',
data
});
}

View File

@ -375,6 +375,13 @@ declare namespace Api {
/** 任务列表 */
type TaskList = Common.PaginatingQueryRecord<Task>;
/** 任务催办操作参数 */
type TaskUrgeOperateParams = CommonType.RecordNullable<{
taskIdList: CommonType.IdType[];
messageType: MessageType[];
message: string;
}>;
/** 任务操作类型 */
type TaskOperateType = 'delegateTask' | 'transferTask' | 'addSignature' | 'reductionSignature' | 'stopTask';

View File

@ -29,6 +29,7 @@ declare module 'vue' {
FlowInterveneModal: typeof import('./../components/workflow/flow-intervene-modal.vue')['default']
FlowPreview: typeof import('./../components/workflow/flow-preview.vue')['default']
FlowTaskApprovalModal: typeof import('./../components/workflow/flow-task-approval-modal.vue')['default']
FlowUrgeModal: typeof import('./../components/workflow/flow-urge-modal.vue')['default']
FormTip: typeof import('./../components/custom/form-tip.vue')['default']
FullScreen: typeof import('./../components/common/full-screen.vue')['default']
IconAntDesignEnterOutlined: typeof import('~icons/ant-design/enter-outlined')['default']
@ -45,6 +46,8 @@ declare module 'vue' {
'IconMaterialSymbols:add': typeof import('~icons/material-symbols/add')['default']
'IconMaterialSymbols:deleteOutline': typeof import('~icons/material-symbols/delete-outline')['default']
'IconMaterialSymbols:downloadRounded': typeof import('~icons/material-symbols/download-rounded')['default']
'IconMaterialSymbols:driveFileRenameOutlineOutline': typeof import('~icons/material-symbols/drive-file-rename-outline-outline')['default']
'IconMaterialSymbols:editDocument': typeof import('~icons/material-symbols/edit-document')['default']
'IconMaterialSymbols:imageOutline': typeof import('~icons/material-symbols/image-outline')['default']
'IconMaterialSymbols:refreshRounded': typeof import('~icons/material-symbols/refresh-rounded')['default']
'IconMaterialSymbols:syncOutline': typeof import('~icons/material-symbols/sync-outline')['default']

View File

@ -2,7 +2,7 @@
import { computed, ref, shallowRef, watch } from 'vue';
import { NButton, NDivider, NEmpty, NInput, NRadioButton, NRadioGroup, NTag } from 'naive-ui';
import { useBoolean, useLoading } from '@sa/hooks';
import { fetchGetAllFinishedTask, fetchGetAllWaitingTask } from '@/service/api/workflow/task';
import { fetchGetAllFinishedTask, fetchGetAllWaitingTask, fetchTaskAssignee } from '@/service/api/workflow/task';
import { fetchGetCategoryTree } from '@/service/api/workflow/category';
import { useAppStore } from '@/store/modules/app';
import { useTable, useTableOperate } from '@/hooks/common/table';
@ -28,6 +28,8 @@ useDict('wf_task_status');
const appStore = useAppStore();
const { bool: viewVisible, setTrue: showViewDrawer } = useBoolean();
const { bool: interveneVisible, setTrue: showInterveneDrawer } = useBoolean();
const { bool: urgeVisible, setTrue: showUrgeModal } = useBoolean();
const { bool: assigneeVisible, setTrue: showAssigneeModal } = useBoolean();
const dynamicComponent = shallowRef();
const waitingStatus = ref<boolean>(true);
@ -232,6 +234,14 @@ function handleIntervene(row: Api.Workflow.Task) {
assigneeNames.value = row.assigneeNames?.split(',') || [];
showInterveneDrawer();
}
async function handleAssigneeConfirm(userIds: CommonType.IdType[]) {
const { error } = await fetchTaskAssignee(checkedRowKeys.value, userIds[0]);
if (error) return;
window.$message?.success('修改办理人成功');
assigneeVisible.value = false;
await getData();
}
</script>
<template>
@ -291,7 +301,36 @@ function handleIntervene(row: Api.Workflow.Task) {
:show-delete="false"
:show-export="false"
@refresh="getData"
></TableHeaderOperation>
>
<template #prefix>
<NButton
v-if="waitingStatus"
:disabled="checkedRowKeys.length === 0"
size="small"
type="warning"
ghost
@click="showAssigneeModal"
>
<template #icon>
<icon-material-symbols:drive-file-rename-outline-outline class="text-icon" />
</template>
修改办理人
</NButton>
<NButton
v-if="waitingStatus"
:disabled="checkedRowKeys.length === 0"
size="small"
type="success"
ghost
@click="showUrgeModal"
>
<template #icon>
<SvgIcon local-icon="bell" class="text-16px" />
</template>
催办
</NButton>
</template>
</TableHeaderOperation>
</template>
<NDataTable
v-model:checked-row-keys="checkedRowKeys"
@ -314,6 +353,10 @@ function handleIntervene(row: Api.Workflow.Task) {
:assignee-names="assigneeNames"
@refresh="getData"
/>
<!-- 催办 -->
<FlowUrgeModal v-model:visible="urgeVisible" :task-ids="checkedRowKeys" @submit="getData" />
<!-- 修改办理人 -->
<UserSelectModal v-model:visible="assigneeVisible" @confirm="handleAssigneeConfirm" />
</NCard>
</div>
</TableSiderLayout>