chore: 流程监控 待办任务
This commit is contained in:
parent
58dac45dde
commit
58c361c908
@ -1,19 +1,33 @@
|
|||||||
|
<!-- eslint-disable no-use-before-define -->
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { FlowInfoResponse } from '#/api/workflow/instance/model';
|
import type { User } from '#/api/system/user/model';
|
||||||
import type { TaskInfo } from '#/api/workflow/task/model';
|
import type { TaskInfo } from '#/api/workflow/task/model';
|
||||||
|
|
||||||
import { computed, onMounted, ref } from 'vue';
|
import { computed, nextTick, onMounted, ref, useTemplateRef } from 'vue';
|
||||||
|
|
||||||
import { Page } from '@vben/common-ui';
|
import { Page } from '@vben/common-ui';
|
||||||
import { useTabs } from '@vben/hooks';
|
import { useTabs } from '@vben/hooks';
|
||||||
|
import { addFullName, getPopupContainer } from '@vben/utils';
|
||||||
|
|
||||||
import { Empty, InputSearch, Segmented } from 'ant-design-vue';
|
import { FilterOutlined, RedoOutlined } from '@ant-design/icons-vue';
|
||||||
import { debounce, uniqueId } from 'lodash-es';
|
import {
|
||||||
|
Empty,
|
||||||
|
Form,
|
||||||
|
FormItem,
|
||||||
|
Input,
|
||||||
|
InputSearch,
|
||||||
|
Popover,
|
||||||
|
Segmented,
|
||||||
|
Spin,
|
||||||
|
Tooltip,
|
||||||
|
TreeSelect,
|
||||||
|
} from 'ant-design-vue';
|
||||||
|
import { cloneDeep, debounce, uniqueId } from 'lodash-es';
|
||||||
|
|
||||||
import { flowInfo } from '#/api/workflow/instance';
|
import { categoryTree } from '#/api/workflow/category';
|
||||||
import { pageByAllTaskFinish, pageByAllTaskWait } from '#/api/workflow/task';
|
import { pageByAllTaskFinish, pageByAllTaskWait } from '#/api/workflow/task';
|
||||||
|
|
||||||
import { ApprovalCard, ApprovalPanel } from '../components';
|
import { ApprovalCard, ApprovalPanel, CopyComponent } from '../components';
|
||||||
|
|
||||||
const emptyImage = Empty.PRESENTED_IMAGE_SIMPLE;
|
const emptyImage = Empty.PRESENTED_IMAGE_SIMPLE;
|
||||||
|
|
||||||
@ -28,6 +42,7 @@ interface TaskItem extends TaskInfo {
|
|||||||
const taskList = ref<TaskItem[]>([]);
|
const taskList = ref<TaskItem[]>([]);
|
||||||
const taskTotal = ref(0);
|
const taskTotal = ref(0);
|
||||||
const page = ref(1);
|
const page = ref(1);
|
||||||
|
const loading = ref(false);
|
||||||
|
|
||||||
const typeOptions = [
|
const typeOptions = [
|
||||||
{ label: '待办任务', value: 'todo' },
|
{ label: '待办任务', value: 'todo' },
|
||||||
@ -47,18 +62,24 @@ const approvalType = computed(() => {
|
|||||||
return 'admin';
|
return 'admin';
|
||||||
});
|
});
|
||||||
async function handleTypeChange() {
|
async function handleTypeChange() {
|
||||||
|
// 需要先滚动到顶部
|
||||||
|
cardContainerRef.value?.scroll({ top: 0, behavior: 'auto' });
|
||||||
page.value = 1;
|
page.value = 1;
|
||||||
const resp = await currentApi.value({ pageSize: 10, pageNum: page.value });
|
|
||||||
taskList.value = resp.rows.map((item) => ({
|
taskList.value = [];
|
||||||
...item,
|
await nextTick();
|
||||||
active: false,
|
await reload(true);
|
||||||
randomId: uniqueId(),
|
|
||||||
}));
|
|
||||||
taskTotal.value = resp.total;
|
|
||||||
// eslint-disable-next-line no-use-before-define
|
|
||||||
currentTask.value = undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const defaultFormData = {
|
||||||
|
flowName: '', // 流程定义名称
|
||||||
|
nodeName: '', // 任务名称
|
||||||
|
flowCode: '', // 流程定义编码
|
||||||
|
createByIds: [] as string[], // 创建人
|
||||||
|
category: null as null | number, // 流程分类
|
||||||
|
};
|
||||||
|
const formData = ref(cloneDeep(defaultFormData));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否已经加载全部数据 即 taskList.length === taskTotal
|
* 是否已经加载全部数据 即 taskList.length === taskTotal
|
||||||
*/
|
*/
|
||||||
@ -66,19 +87,49 @@ const isLoadComplete = computed(
|
|||||||
() => taskList.value.length === taskTotal.value,
|
() => taskList.value.length === taskTotal.value,
|
||||||
);
|
);
|
||||||
|
|
||||||
onMounted(async () => {
|
// 卡片父容器的ref
|
||||||
/**
|
const cardContainerRef = useTemplateRef('cardContainerRef');
|
||||||
* 获取待办任务列表
|
|
||||||
|
/**
|
||||||
|
* @param resetFields 是否清空查询参数
|
||||||
*/
|
*/
|
||||||
const resp = await currentApi.value({ pageSize: 10, pageNum: page.value });
|
async function reload(resetFields: boolean = false) {
|
||||||
console.log(resp);
|
// 需要先滚动到顶部
|
||||||
|
cardContainerRef.value?.scroll({ top: 0, behavior: 'auto' });
|
||||||
|
|
||||||
|
page.value = 1;
|
||||||
|
currentTask.value = undefined;
|
||||||
|
taskTotal.value = 0;
|
||||||
|
lastSelectId.value = '';
|
||||||
|
|
||||||
|
if (resetFields) {
|
||||||
|
formData.value = cloneDeep(defaultFormData);
|
||||||
|
selectedUserList.value = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
loading.value = true;
|
||||||
|
const resp = await currentApi.value({
|
||||||
|
pageSize: 10,
|
||||||
|
pageNum: page.value,
|
||||||
|
...formData.value,
|
||||||
|
});
|
||||||
taskList.value = resp.rows.map((item) => ({
|
taskList.value = resp.rows.map((item) => ({
|
||||||
...item,
|
...item,
|
||||||
active: false,
|
active: false,
|
||||||
randomId: uniqueId(),
|
randomId: uniqueId(),
|
||||||
}));
|
}));
|
||||||
taskTotal.value = resp.total;
|
taskTotal.value = resp.total;
|
||||||
});
|
|
||||||
|
loading.value = false;
|
||||||
|
// 默认选中第一个
|
||||||
|
if (taskList.value.length > 0) {
|
||||||
|
const firstTask = taskList.value[0]!;
|
||||||
|
currentTask.value = firstTask;
|
||||||
|
handleCardClick(firstTask);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(reload);
|
||||||
|
|
||||||
const handleScroll = debounce(async (e: Event) => {
|
const handleScroll = debounce(async (e: Event) => {
|
||||||
if (!e.target) {
|
if (!e.target) {
|
||||||
@ -93,8 +144,13 @@ const handleScroll = debounce(async (e: Event) => {
|
|||||||
|
|
||||||
// 滚动到底部且没有加载完成
|
// 滚动到底部且没有加载完成
|
||||||
if (isBottom && !isLoadComplete.value) {
|
if (isBottom && !isLoadComplete.value) {
|
||||||
|
loading.value = true;
|
||||||
page.value += 1;
|
page.value += 1;
|
||||||
const resp = await currentApi.value({ pageSize: 10, pageNum: page.value });
|
const resp = await currentApi.value({
|
||||||
|
pageSize: 10,
|
||||||
|
pageNum: page.value,
|
||||||
|
...formData.value,
|
||||||
|
});
|
||||||
taskList.value.push(
|
taskList.value.push(
|
||||||
...resp.rows.map((item) => ({
|
...resp.rows.map((item) => ({
|
||||||
...item,
|
...item,
|
||||||
@ -102,15 +158,14 @@ const handleScroll = debounce(async (e: Event) => {
|
|||||||
randomId: uniqueId(),
|
randomId: uniqueId(),
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
|
loading.value = false;
|
||||||
}
|
}
|
||||||
}, 200);
|
}, 200);
|
||||||
|
|
||||||
const currentInstance = ref<FlowInfoResponse>();
|
|
||||||
|
|
||||||
const lastSelectId = ref('');
|
const lastSelectId = ref('');
|
||||||
const currentTask = ref<TaskInfo>();
|
const currentTask = ref<TaskInfo>();
|
||||||
async function handleCardClick(item: TaskItem) {
|
async function handleCardClick(item: TaskItem) {
|
||||||
const { randomId, businessId } = item;
|
const { randomId } = item;
|
||||||
// 点击的是同一个
|
// 点击的是同一个
|
||||||
if (lastSelectId.value === randomId) {
|
if (lastSelectId.value === randomId) {
|
||||||
return;
|
return;
|
||||||
@ -121,19 +176,33 @@ async function handleCardClick(item: TaskItem) {
|
|||||||
item.active = item.randomId === randomId;
|
item.active = item.randomId === randomId;
|
||||||
});
|
});
|
||||||
lastSelectId.value = randomId;
|
lastSelectId.value = randomId;
|
||||||
|
|
||||||
const resp = await flowInfo(businessId);
|
|
||||||
currentInstance.value = resp;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const { refreshTab } = useTabs();
|
const { refreshTab } = useTabs();
|
||||||
|
|
||||||
|
// 由于失去焦点浮层会消失 使用v-model选择人员完毕后强制显示
|
||||||
|
const popoverOpen = ref(false);
|
||||||
|
const selectedUserList = ref<User[]>([]);
|
||||||
|
function handleFinish(userList: User[]) {
|
||||||
|
popoverOpen.value = true;
|
||||||
|
selectedUserList.value = userList;
|
||||||
|
formData.value.createByIds = userList.map((item) => item.userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
const treeData = ref<any[]>([]);
|
||||||
|
onMounted(async () => {
|
||||||
|
// menu
|
||||||
|
const tree = await categoryTree();
|
||||||
|
addFullName(tree, 'label', ' / ');
|
||||||
|
treeData.value = tree;
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Page :auto-content-height="true">
|
<Page :auto-content-height="true">
|
||||||
<div class="flex h-full gap-2">
|
<div class="flex h-full gap-2">
|
||||||
<div
|
<div
|
||||||
class="bg-background flex h-full min-w-[320px] max-w-[320px] flex-col rounded-lg"
|
class="bg-background relative flex h-full min-w-[320px] max-w-[320px] flex-col rounded-lg"
|
||||||
>
|
>
|
||||||
<!-- 搜索条件 -->
|
<!-- 搜索条件 -->
|
||||||
<div
|
<div
|
||||||
@ -146,9 +215,89 @@ const { refreshTab } = useTabs();
|
|||||||
class="mb-2"
|
class="mb-2"
|
||||||
@change="handleTypeChange"
|
@change="handleTypeChange"
|
||||||
/>
|
/>
|
||||||
<InputSearch placeholder="搜索还没做" />
|
<div class="flex items-center gap-1">
|
||||||
|
<InputSearch
|
||||||
|
v-model:value="formData.flowName"
|
||||||
|
placeholder="流程名称搜索"
|
||||||
|
@search="reload(false)"
|
||||||
|
/>
|
||||||
|
<Tooltip placement="top" title="重置">
|
||||||
|
<a-button @click="reload(true)">
|
||||||
|
<RedoOutlined />
|
||||||
|
</a-button>
|
||||||
|
</Tooltip>
|
||||||
|
<Popover
|
||||||
|
v-model:open="popoverOpen"
|
||||||
|
:get-popup-container="getPopupContainer"
|
||||||
|
placement="rightTop"
|
||||||
|
trigger="click"
|
||||||
|
>
|
||||||
|
<template #title>
|
||||||
|
<div class="w-full border-b pb-[12px] text-[16px]">搜索</div>
|
||||||
|
</template>
|
||||||
|
<template #content>
|
||||||
|
<Form
|
||||||
|
:colon="false"
|
||||||
|
:label-col="{ span: 6 }"
|
||||||
|
:model="formData"
|
||||||
|
autocomplete="off"
|
||||||
|
class="w-[300px]"
|
||||||
|
@finish="() => reload(false)"
|
||||||
|
>
|
||||||
|
<FormItem label="申请人">
|
||||||
|
<!-- 弹窗关闭后仍然显示表单浮层 -->
|
||||||
|
<CopyComponent
|
||||||
|
v-model:user-list="selectedUserList"
|
||||||
|
@cancel="() => (popoverOpen = true)"
|
||||||
|
@finish="handleFinish"
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label="流程分类">
|
||||||
|
<TreeSelect
|
||||||
|
v-model:value="formData.category"
|
||||||
|
:allow-clear="true"
|
||||||
|
:field-names="{ label: 'label', value: 'id' }"
|
||||||
|
:get-popup-container="getPopupContainer"
|
||||||
|
:tree-data="treeData"
|
||||||
|
:tree-default-expand-all="true"
|
||||||
|
:tree-line="{ showLeafIcon: false }"
|
||||||
|
placeholder="请选择"
|
||||||
|
tree-node-filter-prop="label"
|
||||||
|
tree-node-label-prop="fullName"
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label="任务名称">
|
||||||
|
<Input
|
||||||
|
v-model:value="formData.nodeName"
|
||||||
|
placeholder="请输入"
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem label="流程编码">
|
||||||
|
<Input
|
||||||
|
v-model:value="formData.flowCode"
|
||||||
|
placeholder="请输入"
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem>
|
||||||
|
<div class="flex">
|
||||||
|
<a-button block html-type="submit" type="primary">
|
||||||
|
搜索
|
||||||
|
</a-button>
|
||||||
|
<a-button block class="ml-2" @click="reload(true)">
|
||||||
|
重置
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
|
</FormItem>
|
||||||
|
</Form>
|
||||||
|
</template>
|
||||||
|
<a-button>
|
||||||
|
<FilterOutlined />
|
||||||
|
</a-button>
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
ref="cardContainerRef"
|
||||||
class="thin-scrollbar flex flex-1 flex-col gap-2 overflow-y-auto py-3"
|
class="thin-scrollbar flex flex-1 flex-col gap-2 overflow-y-auto py-3"
|
||||||
@scroll="handleScroll"
|
@scroll="handleScroll"
|
||||||
>
|
>
|
||||||
@ -163,6 +312,19 @@ const { refreshTab } = useTabs();
|
|||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<Empty v-else :image="emptyImage" />
|
<Empty v-else :image="emptyImage" />
|
||||||
|
<div
|
||||||
|
v-if="isLoadComplete && taskList.length > 0"
|
||||||
|
class="flex items-center justify-center text-[14px] opacity-50"
|
||||||
|
>
|
||||||
|
没有更多数据了
|
||||||
|
</div>
|
||||||
|
<!-- 遮罩loading层 -->
|
||||||
|
<div
|
||||||
|
v-if="loading"
|
||||||
|
class="absolute left-0 top-0 flex h-full w-full items-center justify-center bg-[rgba(0,0,0,0.1)]"
|
||||||
|
>
|
||||||
|
<Spin tip="加载中..." />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- total显示 -->
|
<!-- total显示 -->
|
||||||
<div
|
<div
|
||||||
|
Loading…
Reference in New Issue
Block a user