| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364 |
- <!-- eslint-disable no-use-before-define -->
- <script setup lang="ts">
- import type { User } from '#/api/system/user/model';
- import type { TaskInfo } from '#/api/workflow/task/model';
- import { computed, nextTick, onMounted, ref, useTemplateRef } from 'vue';
- import { Page } from '@vben/common-ui';
- import { useTabs } from '@vben/hooks';
- <<<<<<< HEAD
- import { Filter, Refresh } from '@element-plus/icons-vue';
- import {
- ElButton,
- ElForm,
- ElFormItem,
- ElInput,
- ElMessage,
- ElPopover,
- ElTooltip,
- } from 'element-plus';
- =======
- // import {
- // Empty,
- // Form,
- // FormItem,
- // Input,
- // InputSearch,
- // Popover,
- // Segmented,
- // Spin,
- // Tooltip,
- // TreeSelect,
- // } from 'ant-design-vue';
- >>>>>>> 0a5e399be8a3dc56c82fd915b6765781a5d30748
- import { cloneDeep, debounce, uniqueId } from 'lodash-es';
- import { categoryTree } from '#/api/workflow/category';
- import { pageByAllTaskFinish, pageByAllTaskWait } from '#/api/workflow/task';
- import { ApprovalCard, ApprovalPanel, CopyComponent } from '../components';
- import { bottomOffset } from './constant';
- const emptyImage = Empty.PRESENTED_IMAGE_SIMPLE;
- /**
- * 流程监控 - 待办任务页面的id不唯一 改为前端处理
- */
- interface TaskItem extends TaskInfo {
- active: boolean;
- randomId: string;
- }
- const taskList = ref<TaskItem[]>([]);
- const taskTotal = ref(0);
- const page = ref(1);
- const loading = ref(false);
- const typeOptions = [
- { label: '待办任务', value: 'todo' },
- { label: '已办任务', value: 'done' },
- ];
- const currentType = ref('todo');
- const currentApi = computed(() => {
- if (currentType.value === 'todo') {
- return pageByAllTaskWait;
- }
- return pageByAllTaskFinish;
- });
- const approvalType = computed(() => {
- if (currentType.value === 'done') {
- return 'readonly';
- }
- return 'admin';
- });
- async function handleTypeChange() {
- // 需要先滚动到顶部
- cardContainerRef.value?.scroll({ top: 0, behavior: 'auto' });
- page.value = 1;
- taskList.value = [];
- await nextTick();
- await reload(true);
- }
- const defaultFormData = {
- flowName: '', // 流程定义名称
- nodeName: '', // 任务名称
- flowCode: '', // 流程定义编码
- createByIds: [] as string[], // 创建人
- category: null as null | number, // 流程分类
- };
- const formData = ref(cloneDeep(defaultFormData));
- /**
- * 是否已经加载全部数据 即 taskList.length === taskTotal
- */
- const isLoadComplete = computed(
- () => taskList.value.length === taskTotal.value,
- );
- // 卡片父容器的ref
- const cardContainerRef = useTemplateRef('cardContainerRef');
- /**
- * @param resetFields 是否清空查询参数
- */
- async function reload(resetFields: boolean = false) {
- // 需要先滚动到顶部
- 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) => ({
- ...item,
- active: false,
- randomId: uniqueId(),
- }));
- 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) => {
- if (!e.target) {
- return;
- }
- // e.target.scrollTop 是元素顶部到当前可视区域顶部的距离,即已滚动的高度。
- // e.target.clientHeight 是元素的可视高度。
- // e.target.scrollHeight 是元素的总高度。
- const { scrollTop, clientHeight, scrollHeight } = e.target as HTMLElement;
- // 判断是否滚动到底部
- const isBottom = scrollTop + clientHeight >= scrollHeight - bottomOffset;
- console.log('scrollTop + clientHeight', scrollTop + clientHeight);
- console.log('scrollHeight', scrollHeight);
- // 滚动到底部且没有加载完成
- if (isBottom && !isLoadComplete.value) {
- loading.value = true;
- try {
- page.value += 1;
- const resp = await currentApi.value({
- pageSize: 10,
- pageNum: page.value,
- ...formData.value,
- });
- taskList.value.push(
- ...resp.rows.map((item) => ({
- ...item,
- active: false,
- randomId: uniqueId(),
- })),
- );
- } catch {
- ElMessage.error('加载更多任务失败');
- } finally {
- loading.value = false;
- }
- }
- }, 200);
- const lastSelectId = ref('');
- const currentTask = ref<TaskInfo>();
- async function handleCardClick(item: TaskItem) {
- const { randomId } = item;
- // 点击的是同一个
- if (lastSelectId.value === randomId) {
- return;
- }
- currentTask.value = item;
- // 反选状态 & 如果已经点击了 不变 & 保持只能有一个选中
- taskList.value.forEach((item) => {
- item.active = item.randomId === randomId;
- });
- lastSelectId.value = randomId;
- }
- 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>
- <template>
- <Page :auto-content-height="true">
- <div class="flex h-full gap-2">
- <div
- class="bg-background relative flex h-full min-w-[320px] max-w-[320px] flex-col rounded-lg"
- >
- <!-- 搜索条件 -->
- <div
- class="bg-background z-100 sticky left-0 top-0 w-full rounded-t-lg border-b-[1px] border-solid p-2"
- >
- <div class="mb-2 flex items-center gap-1">
- <ElButton
- :type="currentType === 'todo' ? 'primary' : 'default'"
- @click="
- currentType = 'todo';
- handleTypeChange();
- "
- >
- 待办任务
- </ElButton>
- <ElButton
- :type="currentType === 'done' ? 'primary' : 'default'"
- @click="
- currentType = 'done';
- handleTypeChange();
- "
- >
- 已办任务
- </ElButton>
- </div>
- <div class="flex items-center gap-1">
- <ElInput
- v-model="formData.flowName"
- placeholder="流程名称搜索"
- clearable
- class="w-full"
- @keyup.enter="reload(false)"
- />
- <ElTooltip placement="top" content="重置">
- <ElButton @click="reload(true)" circle>
- <Refresh />
- </ElButton>
- </ElTooltip>
- <ElPopover placement="top-end" trigger="click">
- <template #reference>
- <ElButton circle>
- <Filter />
- </ElButton>
- </template>
- <template #default>
- <ElForm
- :model="formData"
- label-width="80px"
- class="w-[300px]"
- @submit.prevent="reload(false)"
- >
- <ElFormItem label="申请人">
- <CopyComponent
- v-model:user-list="selectedUserList"
- @cancel="() => (popoverOpen = true)"
- @finish="handleFinish"
- />
- </ElFormItem>
- <ElFormItem label="流程分类">
- <ElInput v-model="formData.category" placeholder="请选择" />
- </ElFormItem>
- <ElFormItem label="任务名称">
- <ElInput v-model="formData.nodeName" placeholder="请输入" />
- </ElFormItem>
- <ElFormItem label="流程编码">
- <ElInput v-model="formData.flowCode" placeholder="请输入" />
- </ElFormItem>
- <ElFormItem>
- <div class="flex justify-end gap-2">
- <ElButton type="primary" @click="reload(false)">
- 搜索
- </ElButton>
- <ElButton @click="reload(true)"> 重置 </ElButton>
- </div>
- </ElFormItem>
- </ElForm>
- </template>
- </ElPopover>
- </div>
- </div>
- <div
- ref="cardContainerRef"
- class="thin-scrollbar flex flex-1 flex-col gap-2 overflow-y-auto py-3"
- @scroll="handleScroll"
- >
- <template v-if="taskList.length > 0">
- <ApprovalCard
- v-for="item in taskList"
- :key="item.randomId"
- :info="item"
- class="mx-2"
- row-key="randomId"
- @click="handleCardClick(item)"
- />
- </template>
- <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>
- <!-- total显示 -->
- <div
- class="bg-background sticky bottom-0 w-full rounded-b-lg border-t-[1px] py-2"
- >
- <div class="flex items-center justify-center">
- 共 {{ taskTotal }} 条记录
- </div>
- </div>
- </div>
- <ApprovalPanel
- :task="currentTask"
- :type="approvalType"
- @reload="refreshTab"
- />
- </div>
- </Page>
- </template>
- <style lang="scss" scoped>
- .thin-scrollbar {
- &::-webkit-scrollbar {
- width: 5px;
- }
- }
- :deep(.el-card__body) {
- @apply thin-scrollbar;
- }
- </style>
|