chore: 我的待办 & 请假
This commit is contained in:
parent
8f9215ffad
commit
e6e2c84f45
14
apps/web-antd/src/api/workflow/instance/index.ts
Normal file
14
apps/web-antd/src/api/workflow/instance/index.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import type { FlowInfoResponse } from './model';
|
||||
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
/**
|
||||
* 获取流程图,流程记录
|
||||
* @param businessId 业务标识
|
||||
* @returns 流程图,流程记录
|
||||
*/
|
||||
export function flowInfo(businessId: string) {
|
||||
return requestClient.get<FlowInfoResponse>(
|
||||
`/workflow/instance/flowImage/${businessId}`,
|
||||
);
|
||||
}
|
41
apps/web-antd/src/api/workflow/instance/model.d.ts
vendored
Normal file
41
apps/web-antd/src/api/workflow/instance/model.d.ts
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
export interface Flow {
|
||||
id: string;
|
||||
createTime: string;
|
||||
updateTime: string;
|
||||
tenantId: string;
|
||||
delFlag: string;
|
||||
definitionId: string;
|
||||
flowName?: any;
|
||||
instanceId: string;
|
||||
taskId: string;
|
||||
cooperateType: number;
|
||||
cooperateTypeName: string;
|
||||
businessId?: any;
|
||||
nodeCode: string;
|
||||
nodeName: string;
|
||||
nodeType: number;
|
||||
targetNodeCode: string;
|
||||
targetNodeName: string;
|
||||
approver: string;
|
||||
approveName: string;
|
||||
collaborator?: any;
|
||||
permissionList?: any;
|
||||
skipType: string;
|
||||
flowStatus: string;
|
||||
flowTaskStatus?: any;
|
||||
flowStatusName?: any;
|
||||
message: string;
|
||||
ext?: any;
|
||||
createBy?: any;
|
||||
formCustom: string;
|
||||
formPath: string;
|
||||
flowCode?: any;
|
||||
version?: any;
|
||||
runDuration: string;
|
||||
nickName?: any;
|
||||
}
|
||||
|
||||
export interface FlowInfoResponse {
|
||||
image: string;
|
||||
list: Flow[];
|
||||
}
|
85
apps/web-antd/src/api/workflow/task/index.ts
Normal file
85
apps/web-antd/src/api/workflow/task/index.ts
Normal file
@ -0,0 +1,85 @@
|
||||
import type { TaskInfo } from './model';
|
||||
|
||||
import type { PageQuery, PageResult } from '#/api/common';
|
||||
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
/**
|
||||
* 启动任务
|
||||
* @param data
|
||||
*/
|
||||
export function startWorkFlow(data: any) {
|
||||
return requestClient.postWithMsg<void>('/workflow/task/startWorkFlow', data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 办理任务
|
||||
* @param data
|
||||
*/
|
||||
export function completeTask(data: any) {
|
||||
return requestClient.postWithMsg<void>('/workflow/task/completeTask', data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询当前用户的待办任务
|
||||
* @param params
|
||||
*/
|
||||
export function pageByTaskWait(params?: PageQuery) {
|
||||
return requestClient.get<PageResult<TaskInfo>>(
|
||||
'/workflow/task/pageByTaskWait',
|
||||
{ params },
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询当前用户的已办任务
|
||||
* @param params
|
||||
*/
|
||||
export function pageByTaskFinish(params?: PageQuery) {
|
||||
return requestClient.get<PageResult<TaskInfo>>(
|
||||
'/workflow/task/pageByTaskFinish',
|
||||
{ params },
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询所有待办任务
|
||||
* @param params
|
||||
*/
|
||||
export function pageByAllTaskWait(params?: PageQuery) {
|
||||
return requestClient.get<PageResult<TaskInfo>>(
|
||||
'/workflow/task/pageByAllTaskWait',
|
||||
{ params },
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询已办任务
|
||||
* @param params
|
||||
*/
|
||||
export function pageByAllTaskFinish(params?: PageQuery) {
|
||||
return requestClient.get<PageResult<TaskInfo>>(
|
||||
'/workflow/task/pageByAllTaskFinish',
|
||||
{ params },
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询当前用户的抄送
|
||||
* @param params
|
||||
*/
|
||||
export function pageByTaskCopy(params?: PageQuery) {
|
||||
return requestClient.get<PageResult<TaskInfo>>(
|
||||
'/workflow/task/pageByTaskCopy',
|
||||
{ params },
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据taskId查询代表任务
|
||||
* @param taskId 任务id
|
||||
* @returns info
|
||||
*/
|
||||
export function getTaskByTaskId(taskId: string) {
|
||||
return requestClient.get<TaskInfo>(`/workflow/task/${taskId}`);
|
||||
}
|
27
apps/web-antd/src/api/workflow/task/model.d.ts
vendored
Normal file
27
apps/web-antd/src/api/workflow/task/model.d.ts
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
export interface TaskInfo {
|
||||
id: string;
|
||||
createTime: string;
|
||||
updateTime: string;
|
||||
tenantId: string;
|
||||
delFlag?: any;
|
||||
definitionId: string;
|
||||
instanceId: string;
|
||||
flowName: string;
|
||||
businessId: string;
|
||||
nodeCode: string;
|
||||
nodeName: string;
|
||||
nodeType: number;
|
||||
permissionList?: any;
|
||||
userList?: any;
|
||||
formCustom: string;
|
||||
formPath: string;
|
||||
flowCode: string;
|
||||
version: string;
|
||||
flowStatus: string;
|
||||
flowStatusName: string;
|
||||
transactorNames: string;
|
||||
processedBy: string;
|
||||
type: string;
|
||||
nodeRatio?: any;
|
||||
nickName: string;
|
||||
}
|
@ -129,6 +129,39 @@ const profileRoute: RouteRecordStringComponent[] = [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
component: 'BasicLayout',
|
||||
meta: {
|
||||
hideChildrenInMenu: true,
|
||||
hideInMenu: true,
|
||||
title: '请假申请',
|
||||
},
|
||||
name: 'WorkflowLeave',
|
||||
path: '/',
|
||||
redirect: '/workflow/leave',
|
||||
children: [
|
||||
{
|
||||
component: 'workflow/leave/leave-form',
|
||||
meta: {
|
||||
icon: 'eos-icons:role-binding-outlined',
|
||||
keepAlive: true,
|
||||
title: '请假申请',
|
||||
},
|
||||
name: 'WorkflowLeaveIndex',
|
||||
path: '/workflow/leave',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
component: 'workflow/leave/leave-form',
|
||||
meta: {
|
||||
icon: 'eos-icons:role-binding-outlined',
|
||||
keepAlive: true,
|
||||
title: '请假申请',
|
||||
},
|
||||
name: 'WorkflowLeaveInner',
|
||||
path: '/workflow/leave-inner',
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -1,13 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import { Avatar, Descriptions, DescriptionsItem, Tag } from 'ant-design-vue';
|
||||
import type { TaskInfo } from '#/api/workflow/task/model';
|
||||
|
||||
interface Props {
|
||||
id: string;
|
||||
endTime: string;
|
||||
startTime: string;
|
||||
title: string;
|
||||
desc: string;
|
||||
status: string;
|
||||
import { VbenAvatar } from '@vben/common-ui';
|
||||
import { DictEnum } from '@vben/constants';
|
||||
|
||||
import { Descriptions, DescriptionsItem } from 'ant-design-vue';
|
||||
|
||||
import { renderDict } from '#/utils/render';
|
||||
|
||||
interface Props extends TaskInfo {
|
||||
active: boolean;
|
||||
}
|
||||
|
||||
@ -29,23 +30,28 @@ function handleClick() {
|
||||
class="cursor-pointer rounded-lg border-[1px] border-solid p-3 transition-shadow duration-300 ease-in-out hover:shadow-lg"
|
||||
@click.stop="handleClick"
|
||||
>
|
||||
<Descriptions :column="1" :title="info.title" size="middle">
|
||||
<Descriptions :column="1" :title="info.flowName" size="middle">
|
||||
<template #extra>
|
||||
<Tag color="warning">审批中</Tag>
|
||||
<component
|
||||
:is="renderDict(info.flowStatus, DictEnum.WF_BUSINESS_STATUS)"
|
||||
/>
|
||||
</template>
|
||||
<DescriptionsItem label="描述">{{ info.desc }}</DescriptionsItem>
|
||||
<DescriptionsItem label="开始时间">{{ info.startTime }}</DescriptionsItem>
|
||||
<DescriptionsItem label="结束时间">{{ info.endTime }}</DescriptionsItem>
|
||||
<DescriptionsItem label="当前节点名称">
|
||||
<div class="font-bold">{{ info.nodeName }}</div>
|
||||
</DescriptionsItem>
|
||||
<DescriptionsItem label="开始时间">
|
||||
{{ info.createTime }}
|
||||
</DescriptionsItem>
|
||||
<!-- <DescriptionsItem label="更新时间">
|
||||
{{ info.updateTime }}
|
||||
</DescriptionsItem> -->
|
||||
</Descriptions>
|
||||
<div class="flex items-center justify-between text-[14px]">
|
||||
<div class="flex items-center gap-1">
|
||||
<Avatar
|
||||
size="small"
|
||||
src="https://plus.dapdap.top/minio-server/plus/2024/11/21/925ed278e2d441beb7f695b41e13c4dd.jpg"
|
||||
/>
|
||||
<span class="opacity-50">疯狂的牛子Li</span>
|
||||
<VbenAvatar :alt="info.nickName" class="size-[24px]" src="" />
|
||||
<span class="opacity-50">{{ info.nickName }}</span>
|
||||
</div>
|
||||
<div class="opacity-50">处理时间: 2022-01-01</div>
|
||||
<div class="opacity-50">更新时间: 2222-22-22</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -1,21 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import type { Flow } from '#/api/workflow/instance/model';
|
||||
|
||||
import { Timeline, TimelineItem } from 'ant-design-vue';
|
||||
|
||||
/**
|
||||
* TODO: 仅为demo 后期会替换
|
||||
*/
|
||||
import { VbenAvatar } from '../../../../../../packages/@core/ui-kit/shadcn-ui/src/components';
|
||||
|
||||
interface ApprovalItem {
|
||||
id: string;
|
||||
name: string;
|
||||
status: string;
|
||||
remark?: string;
|
||||
time: string;
|
||||
}
|
||||
import { VbenAvatar } from '@vben/common-ui';
|
||||
|
||||
const props = defineProps<{
|
||||
list: ApprovalItem[];
|
||||
list: Flow[];
|
||||
}>();
|
||||
</script>
|
||||
|
||||
@ -24,10 +18,7 @@ const props = defineProps<{
|
||||
<TimelineItem v-for="item in props.list" :key="item.id">
|
||||
<template #dot>
|
||||
<div class="relative rounded-full border">
|
||||
<VbenAvatar
|
||||
class="size-[36px]"
|
||||
src="https://plus.dapdap.top/minio-server/plus/2024/11/21/925ed278e2d441beb7f695b41e13c4dd.jpg"
|
||||
/>
|
||||
<VbenAvatar :alt="item.approveName" class="size-[36px]" src="" />
|
||||
<div
|
||||
class="border-background absolute bottom-0 right-0 size-[16px] rounded-full border-2 bg-green-500 content-['']"
|
||||
>
|
||||
@ -37,12 +28,12 @@ const props = defineProps<{
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div class="ml-2 flex flex-col">
|
||||
<div>发起人</div>
|
||||
<div>疯狂的牛子Li</div>
|
||||
<div>2022-01-01 12:00:00</div>
|
||||
<div class="rounded-lg border p-1">
|
||||
<span class="opacity-70">这里是备注信息</span>
|
||||
<div class="ml-2 flex flex-col gap-0.5">
|
||||
<div class="font-bold">{{ item.nodeName }}</div>
|
||||
<div>{{ item.approveName }}</div>
|
||||
<div>{{ item.updateTime }}</div>
|
||||
<div v-if="item.message" class="rounded-lg border p-1">
|
||||
<span class="opacity-70">{{ item.message }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</TimelineItem>
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 26 KiB |
62
apps/web-antd/src/views/workflow/leave/api/index.ts
Normal file
62
apps/web-antd/src/views/workflow/leave/api/index.ts
Normal file
@ -0,0 +1,62 @@
|
||||
import type { LeaveForm, LeaveQuery, LeaveVO } from './model';
|
||||
|
||||
import type { ID, IDS, PageResult } from '#/api/common';
|
||||
|
||||
import { commonExport } from '#/api/helper';
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
/**
|
||||
* 查询请假申请列表
|
||||
* @param params
|
||||
* @returns 请假申请列表
|
||||
*/
|
||||
export function leaveList(params?: LeaveQuery) {
|
||||
return requestClient.get<PageResult<LeaveVO>>('/workflow/leave/list', {
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出请假申请列表
|
||||
* @param params
|
||||
* @returns 请假申请列表
|
||||
*/
|
||||
export function leaveExport(params?: LeaveQuery) {
|
||||
return commonExport('/workflow/leave/export', params ?? {});
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询请假申请详情
|
||||
* @param id id
|
||||
* @returns 请假申请详情
|
||||
*/
|
||||
export function leaveInfo(id: ID) {
|
||||
return requestClient.get<LeaveVO>(`/workflow/leave/${id}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增请假申请
|
||||
* @param data
|
||||
* @returns void
|
||||
*/
|
||||
export function leaveAdd(data: LeaveForm) {
|
||||
return requestClient.postWithMsg<void>('/workflow/leave', data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新请假申请
|
||||
* @param data
|
||||
* @returns void
|
||||
*/
|
||||
export function leaveUpdate(data: LeaveForm) {
|
||||
return requestClient.putWithMsg<void>('/workflow/leave', data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除请假申请
|
||||
* @param id id
|
||||
* @returns void
|
||||
*/
|
||||
export function leaveRemove(id: ID | IDS) {
|
||||
return requestClient.deleteWithMsg<void>(`/workflow/leave/${id}`);
|
||||
}
|
107
apps/web-antd/src/views/workflow/leave/api/model.d.ts
vendored
Normal file
107
apps/web-antd/src/views/workflow/leave/api/model.d.ts
vendored
Normal file
@ -0,0 +1,107 @@
|
||||
import type { BaseEntity, PageQuery } from '#/api/common';
|
||||
|
||||
export interface LeaveVO {
|
||||
/**
|
||||
* 主键
|
||||
*/
|
||||
id: number | string;
|
||||
|
||||
/**
|
||||
* 请假类型
|
||||
*/
|
||||
leaveType: string;
|
||||
|
||||
/**
|
||||
* 开始时间
|
||||
*/
|
||||
startDate: string;
|
||||
|
||||
/**
|
||||
* 结束时间
|
||||
*/
|
||||
endDate: string;
|
||||
|
||||
/**
|
||||
* 请假天数
|
||||
*/
|
||||
leaveDays: number;
|
||||
|
||||
/**
|
||||
* 请假原因
|
||||
*/
|
||||
remark: string;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
status: string;
|
||||
}
|
||||
|
||||
export interface LeaveForm extends BaseEntity {
|
||||
/**
|
||||
* 主键
|
||||
*/
|
||||
id?: number | string;
|
||||
|
||||
/**
|
||||
* 请假类型
|
||||
*/
|
||||
leaveType?: string;
|
||||
|
||||
/**
|
||||
* 开始时间
|
||||
*/
|
||||
startDate?: string;
|
||||
|
||||
/**
|
||||
* 结束时间
|
||||
*/
|
||||
endDate?: string;
|
||||
|
||||
/**
|
||||
* 请假天数
|
||||
*/
|
||||
leaveDays?: number;
|
||||
|
||||
/**
|
||||
* 请假原因
|
||||
*/
|
||||
remark?: string;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
status?: string;
|
||||
}
|
||||
|
||||
export interface LeaveQuery extends PageQuery {
|
||||
/**
|
||||
* 请假类型
|
||||
*/
|
||||
leaveType?: string;
|
||||
|
||||
/**
|
||||
* 开始时间
|
||||
*/
|
||||
startDate?: string;
|
||||
|
||||
/**
|
||||
* 结束时间
|
||||
*/
|
||||
endDate?: string;
|
||||
|
||||
/**
|
||||
* 请假天数
|
||||
*/
|
||||
leaveDays?: number;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
status?: string;
|
||||
|
||||
/**
|
||||
* 日期范围参数
|
||||
*/
|
||||
params?: any;
|
||||
}
|
135
apps/web-antd/src/views/workflow/leave/data.tsx
Normal file
135
apps/web-antd/src/views/workflow/leave/data.tsx
Normal file
@ -0,0 +1,135 @@
|
||||
import type { FormSchemaGetter } from '#/adapter/form';
|
||||
import type { VxeGridProps } from '#/adapter/vxe-table';
|
||||
|
||||
import { DictEnum } from '@vben/constants';
|
||||
import { getPopupContainer } from '@vben/utils';
|
||||
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
import { OptionsTag } from '#/components/table';
|
||||
import { renderDict } from '#/utils/render';
|
||||
|
||||
const leaveTypeOptions = [
|
||||
{ label: '病假', value: '1' },
|
||||
{ label: '事假', value: '2' },
|
||||
{ label: '年假', value: '3' },
|
||||
{ label: '婚假', value: '4' },
|
||||
{ label: '产假', value: '5' },
|
||||
{ label: '其他', value: '7' },
|
||||
];
|
||||
|
||||
export const querySchema: FormSchemaGetter = () => [
|
||||
{
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
min: 1,
|
||||
},
|
||||
fieldName: 'startLeaveDays',
|
||||
label: '请假天数',
|
||||
},
|
||||
{
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
min: 1,
|
||||
},
|
||||
fieldName: 'endLeaveDays',
|
||||
label: '至',
|
||||
labelClass: 'justify-center',
|
||||
},
|
||||
];
|
||||
|
||||
export const columns: VxeGridProps['columns'] = [
|
||||
{ type: 'checkbox', width: 60 },
|
||||
{
|
||||
title: '请假类型',
|
||||
field: 'leaveType',
|
||||
slots: {
|
||||
default: ({ row }) => {
|
||||
return <OptionsTag options={leaveTypeOptions} value={row.leaveType} />;
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '开始时间',
|
||||
field: 'startDate',
|
||||
formatter: ({ cellValue }) => dayjs(cellValue).format('YYYY-MM-DD'),
|
||||
},
|
||||
{
|
||||
title: '结束时间',
|
||||
field: 'endDate',
|
||||
formatter: ({ cellValue }) => dayjs(cellValue).format('YYYY-MM-DD'),
|
||||
},
|
||||
{
|
||||
title: '请假天数',
|
||||
field: 'leaveDays',
|
||||
formatter: ({ cellValue }) => `${cellValue}天`,
|
||||
},
|
||||
{
|
||||
title: '请假原因',
|
||||
field: 'remark',
|
||||
},
|
||||
{
|
||||
title: '流程状态',
|
||||
field: 'status',
|
||||
slots: {
|
||||
default: ({ row }) => {
|
||||
return renderDict(row.status, DictEnum.WF_BUSINESS_STATUS);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'action',
|
||||
fixed: 'right',
|
||||
slots: { default: 'action' },
|
||||
title: '操作',
|
||||
width: 180,
|
||||
},
|
||||
];
|
||||
|
||||
export const modalSchema: FormSchemaGetter = () => [
|
||||
{
|
||||
label: '主键',
|
||||
fieldName: 'id',
|
||||
component: 'Input',
|
||||
dependencies: {
|
||||
show: () => false,
|
||||
triggerFields: [''],
|
||||
},
|
||||
},
|
||||
{
|
||||
label: '请假类型',
|
||||
fieldName: 'leaveType',
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
options: leaveTypeOptions,
|
||||
getPopupContainer,
|
||||
},
|
||||
rules: 'selectRequired',
|
||||
},
|
||||
{
|
||||
label: '开始时间',
|
||||
fieldName: 'dateRange',
|
||||
component: 'RangePicker',
|
||||
componentProps: {
|
||||
showTime: true,
|
||||
format: 'YYYY-MM-DD',
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
},
|
||||
rules: 'required',
|
||||
},
|
||||
{
|
||||
label: '请假天数',
|
||||
fieldName: 'leaveDays',
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
disabled: true,
|
||||
},
|
||||
rules: 'required',
|
||||
},
|
||||
{
|
||||
label: '请假原因',
|
||||
fieldName: 'remark',
|
||||
component: 'Textarea',
|
||||
formItemClass: 'items-baseline',
|
||||
},
|
||||
];
|
@ -1,9 +1,170 @@
|
||||
<script setup lang="ts">
|
||||
import CommonSkeleton from '#/views/common';
|
||||
import type { LeaveForm } from './api/model';
|
||||
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import { Page, useVbenModal, type VbenFormProps } from '@vben/common-ui';
|
||||
import { getVxePopupContainer } from '@vben/utils';
|
||||
|
||||
import { Modal, Popconfirm, Space } from 'ant-design-vue';
|
||||
|
||||
import {
|
||||
useVbenVxeGrid,
|
||||
vxeCheckboxChecked,
|
||||
type VxeGridProps,
|
||||
} from '#/adapter/vxe-table';
|
||||
import { commonDownloadExcel } from '#/utils/file/download';
|
||||
|
||||
import { leaveExport, leaveList, leaveRemove } from './api';
|
||||
import { columns, querySchema } from './data';
|
||||
import leaveModal from './leave-modal.vue';
|
||||
|
||||
const formOptions: VbenFormProps = {
|
||||
commonConfig: {
|
||||
labelWidth: 80,
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
schema: querySchema(),
|
||||
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4',
|
||||
};
|
||||
|
||||
const gridOptions: VxeGridProps = {
|
||||
checkboxConfig: {
|
||||
// 高亮
|
||||
highlight: true,
|
||||
// 翻页时保留选中状态
|
||||
reserve: true,
|
||||
// 点击行选中
|
||||
// trigger: 'row',
|
||||
},
|
||||
columns,
|
||||
height: 'auto',
|
||||
keepSource: true,
|
||||
pagerConfig: {},
|
||||
proxyConfig: {
|
||||
ajax: {
|
||||
query: async ({ page }, formValues = {}) => {
|
||||
return await leaveList({
|
||||
pageNum: page.currentPage,
|
||||
pageSize: page.pageSize,
|
||||
...formValues,
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
rowConfig: {
|
||||
keyField: 'id',
|
||||
},
|
||||
// 表格全局唯一表示 保存列配置需要用到
|
||||
id: 'workflow-leave-index',
|
||||
};
|
||||
|
||||
const [BasicTable, tableApi] = useVbenVxeGrid({
|
||||
formOptions,
|
||||
gridOptions,
|
||||
});
|
||||
|
||||
const [LeaveModal, modalApi] = useVbenModal({
|
||||
connectedComponent: leaveModal,
|
||||
});
|
||||
|
||||
const router = useRouter();
|
||||
function handleAdd() {
|
||||
router.push('/workflow/leave');
|
||||
}
|
||||
|
||||
async function handleEdit(row: Required<LeaveForm>) {
|
||||
modalApi.setData({ id: row.id });
|
||||
modalApi.open();
|
||||
}
|
||||
|
||||
async function handleDelete(row: Required<LeaveForm>) {
|
||||
await leaveRemove(row.id);
|
||||
await tableApi.query();
|
||||
}
|
||||
|
||||
function handleMultiDelete() {
|
||||
const rows = tableApi.grid.getCheckboxRecords();
|
||||
const ids = rows.map((row: Required<LeaveForm>) => row.id);
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
okType: 'danger',
|
||||
content: `确认删除选中的${ids.length}条记录吗?`,
|
||||
onOk: async () => {
|
||||
await leaveRemove(ids);
|
||||
await tableApi.query();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function handleDownloadExcel() {
|
||||
commonDownloadExcel(
|
||||
leaveExport,
|
||||
'请假申请数据',
|
||||
tableApi.formApi.form.values,
|
||||
{
|
||||
fieldMappingTime: formOptions.fieldMappingTime,
|
||||
},
|
||||
);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<CommonSkeleton />
|
||||
</div>
|
||||
<Page :auto-content-height="true">
|
||||
<BasicTable table-title="请假申请列表">
|
||||
<template #toolbar-tools>
|
||||
<Space>
|
||||
<a-button
|
||||
v-access:code="['workflow:leave:export']"
|
||||
@click="handleDownloadExcel"
|
||||
>
|
||||
{{ $t('pages.common.export') }}
|
||||
</a-button>
|
||||
<a-button
|
||||
:disabled="!vxeCheckboxChecked(tableApi)"
|
||||
danger
|
||||
type="primary"
|
||||
v-access:code="['workflow:leave:remove']"
|
||||
@click="handleMultiDelete"
|
||||
>
|
||||
{{ $t('pages.common.delete') }}
|
||||
</a-button>
|
||||
<a-button
|
||||
type="primary"
|
||||
v-access:code="['workflow:leave:add']"
|
||||
@click="handleAdd"
|
||||
>
|
||||
{{ $t('pages.common.add') }}
|
||||
</a-button>
|
||||
</Space>
|
||||
</template>
|
||||
<template #action="{ row }">
|
||||
<Space>
|
||||
<ghost-button
|
||||
v-access:code="['workflow:leave:edit']"
|
||||
@click.stop="handleEdit(row)"
|
||||
>
|
||||
{{ $t('pages.common.edit') }}
|
||||
</ghost-button>
|
||||
<Popconfirm
|
||||
:get-popup-container="getVxePopupContainer"
|
||||
placement="left"
|
||||
title="确认删除?"
|
||||
@confirm="handleDelete(row)"
|
||||
>
|
||||
<ghost-button
|
||||
danger
|
||||
v-access:code="['workflow:leave:remove']"
|
||||
@click.stop=""
|
||||
>
|
||||
{{ $t('pages.common.delete') }}
|
||||
</ghost-button>
|
||||
</Popconfirm>
|
||||
</Space>
|
||||
</template>
|
||||
</BasicTable>
|
||||
<LeaveModal @reload="tableApi.query()" />
|
||||
</Page>
|
||||
</template>
|
||||
|
56
apps/web-antd/src/views/workflow/leave/leave-form.vue
Normal file
56
apps/web-antd/src/views/workflow/leave/leave-form.vue
Normal file
@ -0,0 +1,56 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
import { Card } from 'ant-design-vue';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
import { useVbenForm } from '#/adapter/form';
|
||||
|
||||
import { leaveInfo } from './api';
|
||||
import { modalSchema } from './data';
|
||||
|
||||
const route = useRoute();
|
||||
const disabled = route.query?.readonly === 'true';
|
||||
const id = route.query?.id as string;
|
||||
|
||||
const [BasicForm, formApi] = useVbenForm({
|
||||
commonConfig: {
|
||||
// 默认占满两列
|
||||
formItemClass: 'col-span-2',
|
||||
// 默认label宽度 px
|
||||
labelWidth: 80,
|
||||
// 通用配置项 会影响到所有表单项
|
||||
componentProps: {
|
||||
class: 'w-full',
|
||||
disabled,
|
||||
},
|
||||
},
|
||||
schema: modalSchema(),
|
||||
showDefaultActions: false,
|
||||
wrapperClass: 'grid-cols-2',
|
||||
fieldMappingTime: [
|
||||
[
|
||||
'dateRange',
|
||||
['startDate', 'endDate'],
|
||||
['YYYY-MM-DD 00:00:00', 'YYYY-MM-DD 23:59:59'],
|
||||
],
|
||||
],
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
// 只读 获取信息赋值
|
||||
if (id && disabled) {
|
||||
const resp = await leaveInfo(id);
|
||||
await formApi.setValues(resp);
|
||||
const dateRange = [dayjs(resp.startDate), dayjs(resp.endDate)];
|
||||
await formApi.setFieldValue('dateRange', dateRange);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Card>
|
||||
<BasicForm />
|
||||
</Card>
|
||||
</template>
|
87
apps/web-antd/src/views/workflow/leave/leave-modal.vue
Normal file
87
apps/web-antd/src/views/workflow/leave/leave-modal.vue
Normal file
@ -0,0 +1,87 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
import { cloneDeep } from '@vben/utils';
|
||||
|
||||
import { useVbenForm } from '#/adapter/form';
|
||||
|
||||
import { leaveAdd, leaveInfo, leaveUpdate } from './api';
|
||||
import { modalSchema } from './data';
|
||||
|
||||
const emit = defineEmits<{ reload: [] }>();
|
||||
|
||||
const isUpdate = ref(false);
|
||||
const title = computed(() => {
|
||||
return isUpdate.value ? $t('pages.common.edit') : $t('pages.common.add');
|
||||
});
|
||||
|
||||
const [BasicForm, formApi] = useVbenForm({
|
||||
commonConfig: {
|
||||
// 默认占满两列
|
||||
formItemClass: 'col-span-2',
|
||||
// 默认label宽度 px
|
||||
labelWidth: 80,
|
||||
// 通用配置项 会影响到所有表单项
|
||||
componentProps: {
|
||||
class: 'w-full',
|
||||
},
|
||||
},
|
||||
schema: modalSchema(),
|
||||
showDefaultActions: false,
|
||||
wrapperClass: 'grid-cols-2',
|
||||
});
|
||||
|
||||
const [BasicModal, modalApi] = useVbenModal({
|
||||
fullscreenButton: false,
|
||||
onCancel: handleCancel,
|
||||
onConfirm: handleConfirm,
|
||||
onOpenChange: async (isOpen) => {
|
||||
if (!isOpen) {
|
||||
return null;
|
||||
}
|
||||
modalApi.modalLoading(true);
|
||||
|
||||
const { id } = modalApi.getData() as { id?: number | string };
|
||||
isUpdate.value = !!id;
|
||||
|
||||
if (isUpdate.value && id) {
|
||||
const record = await leaveInfo(id);
|
||||
await formApi.setValues(record);
|
||||
}
|
||||
|
||||
modalApi.modalLoading(false);
|
||||
},
|
||||
});
|
||||
|
||||
async function handleConfirm() {
|
||||
try {
|
||||
modalApi.modalLoading(true);
|
||||
const { valid } = await formApi.validate();
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
// getValues获取为一个readonly的对象 需要修改必须先深拷贝一次
|
||||
const data = cloneDeep(await formApi.getValues());
|
||||
await (isUpdate.value ? leaveUpdate(data) : leaveAdd(data));
|
||||
emit('reload');
|
||||
await handleCancel();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
modalApi.modalLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleCancel() {
|
||||
modalApi.close();
|
||||
await formApi.resetForm();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<BasicModal :close-on-click-modal="false" :title="title" class="w-[550px]">
|
||||
<BasicForm />
|
||||
</BasicModal>
|
||||
</template>
|
@ -1,25 +1,52 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref } from 'vue';
|
||||
import type { FlowInfoResponse } from '#/api/workflow/instance/model';
|
||||
import type { TaskInfo } from '#/api/workflow/task/model';
|
||||
|
||||
import { Page } from '@vben/common-ui';
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
|
||||
import { Fallback, Page, VbenAvatar } from '@vben/common-ui';
|
||||
|
||||
import {
|
||||
Alert,
|
||||
Avatar,
|
||||
Card,
|
||||
Divider,
|
||||
Empty,
|
||||
InputSearch,
|
||||
Space,
|
||||
TabPane,
|
||||
Tabs,
|
||||
Tag,
|
||||
} from 'ant-design-vue';
|
||||
import { debounce, uniqueId } from 'lodash-es';
|
||||
import { debounce } from 'lodash-es';
|
||||
|
||||
import { flowInfo } from '#/api/workflow/instance';
|
||||
import { pageByTaskWait } from '#/api/workflow/task';
|
||||
|
||||
import { ApprovalCard, ApprovalTimeline } from '../components';
|
||||
import RejectionPng from '../components/rejection.png';
|
||||
|
||||
const handleScroll = debounce((e: Event) => {
|
||||
const emptyImage = Empty.PRESENTED_IMAGE_SIMPLE;
|
||||
|
||||
const taskList = ref<({ active: boolean } & TaskInfo)[]>([]);
|
||||
const taskTotal = ref(0);
|
||||
const page = ref(1);
|
||||
|
||||
/**
|
||||
* 是否已经加载全部数据 即 taskList.length === taskTotal
|
||||
*/
|
||||
const isLoadComplete = computed(
|
||||
() => taskList.value.length === taskTotal.value,
|
||||
);
|
||||
|
||||
onMounted(async () => {
|
||||
/**
|
||||
* 获取待办任务列表
|
||||
*/
|
||||
const resp = await pageByTaskWait({ pageSize: 10, pageNum: page.value });
|
||||
console.log(resp);
|
||||
taskList.value = resp.rows.map((item) => ({ ...item, active: false }));
|
||||
taskTotal.value = resp.total;
|
||||
});
|
||||
|
||||
const handleScroll = debounce(async (e: Event) => {
|
||||
if (!e.target) {
|
||||
return;
|
||||
}
|
||||
@ -29,117 +56,139 @@ const handleScroll = debounce((e: Event) => {
|
||||
const { scrollTop, clientHeight, scrollHeight } = e.target as HTMLElement;
|
||||
// 判断是否滚动到底部
|
||||
const isBottom = scrollTop + clientHeight >= scrollHeight;
|
||||
console.log(isBottom);
|
||||
// console.log(scrollTop + clientHeight);
|
||||
// console.log(scrollHeight);
|
||||
|
||||
// 滚动到底部且没有加载完成
|
||||
if (isBottom && !isLoadComplete.value) {
|
||||
page.value += 1;
|
||||
const resp = await pageByTaskWait({ pageSize: 10, pageNum: page.value });
|
||||
taskList.value.push(
|
||||
...resp.rows.map((item) => ({ ...item, active: false })),
|
||||
);
|
||||
}
|
||||
}, 200);
|
||||
|
||||
const data = reactive(
|
||||
Array.from({ length: 10 }).map(() => ({
|
||||
id: uniqueId(),
|
||||
startTime: '2022-01-01',
|
||||
endTime: '2022-01-02',
|
||||
title: '审批任务',
|
||||
desc: '审批任务描述',
|
||||
status: '审批中',
|
||||
active: false,
|
||||
})),
|
||||
);
|
||||
|
||||
const timeLine = Array.from({ length: 5 }).map(() => ({
|
||||
id: uniqueId(),
|
||||
name: '张三',
|
||||
status: '审批中',
|
||||
remark: '审批任务描述',
|
||||
time: '2022-01-01',
|
||||
}));
|
||||
const currentInstance = ref<FlowInfoResponse>();
|
||||
|
||||
const lastSelectId = ref('');
|
||||
function handleCardClick(id: string) {
|
||||
async function handleCardClick(item: TaskInfo) {
|
||||
const { id, businessId } = item;
|
||||
// 点击的是同一个
|
||||
if (lastSelectId.value === id) {
|
||||
return;
|
||||
}
|
||||
// 反选状态 & 如果已经点击了 不变 & 保持只能有一个选中
|
||||
data.forEach((item) => {
|
||||
taskList.value.forEach((item) => {
|
||||
item.active = item.id === id;
|
||||
});
|
||||
lastSelectId.value = id;
|
||||
|
||||
const resp = await flowInfo(businessId);
|
||||
currentInstance.value = resp;
|
||||
}
|
||||
|
||||
const instanceInfo = computed(() => {
|
||||
if (!currentInstance.value) {
|
||||
return;
|
||||
}
|
||||
const length = currentInstance.value.list.length;
|
||||
if (length === 2) {
|
||||
return;
|
||||
}
|
||||
// 最末尾为申请人
|
||||
const info = currentInstance.value.list[length - 1]!;
|
||||
return {
|
||||
id: info.instanceId,
|
||||
createTime: info.createTime,
|
||||
approveName: info.approveName,
|
||||
flowName: info.flowName ?? '未知流程',
|
||||
businessId: '1867081791031750658',
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Page :auto-content-height="true">
|
||||
<div class="flex h-full gap-2">
|
||||
<div class="bg-background flex h-full w-[320px] flex-col rounded-lg">
|
||||
<div
|
||||
class="bg-background 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"
|
||||
>
|
||||
<InputSearch placeholder="搜索" />
|
||||
<InputSearch placeholder="搜索任务名称" />
|
||||
</div>
|
||||
<div
|
||||
class="thin-scrollbar flex flex-1 flex-col gap-2 overflow-y-auto py-3"
|
||||
@scroll="handleScroll"
|
||||
>
|
||||
<ApprovalCard
|
||||
v-for="item in data"
|
||||
:key="item.id"
|
||||
:info="item"
|
||||
class="mx-2"
|
||||
@click="handleCardClick"
|
||||
/>
|
||||
<template v-if="taskList.length > 0">
|
||||
<ApprovalCard
|
||||
v-for="item in taskList"
|
||||
:key="item.id"
|
||||
:info="item"
|
||||
class="mx-2"
|
||||
@click="handleCardClick(item)"
|
||||
/>
|
||||
</template>
|
||||
<Empty v-else :image="emptyImage" />
|
||||
</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">
|
||||
共 {{ data.length }} 条记录
|
||||
共 {{ taskList.length }} 条记录
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Card
|
||||
v-if="currentInstance && instanceInfo"
|
||||
:body-style="{ overflowY: 'auto', height: '100%' }"
|
||||
:title="`编号: ${instanceInfo.id}`"
|
||||
class="thin-scrollbar flex-1 overflow-y-hidden"
|
||||
size="small"
|
||||
title="编号: 1234567890123456789012"
|
||||
>
|
||||
<div class="flex flex-col gap-5 p-4">
|
||||
<div class="flex flex-col gap-3">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="text-2xl font-bold">报销申请</div>
|
||||
<div class="text-2xl font-bold">{{ instanceInfo.flowName }}</div>
|
||||
<div>
|
||||
<Tag color="warning">申请中</Tag>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<Avatar
|
||||
size="small"
|
||||
src="https://plus.dapdap.top/minio-server/plus/2024/11/21/925ed278e2d441beb7f695b41e13c4dd.jpg"
|
||||
<VbenAvatar
|
||||
:alt="instanceInfo.approveName"
|
||||
class="size-[24px]"
|
||||
src=""
|
||||
/>
|
||||
<span>疯狂的牛子Li</span>
|
||||
<span>{{ instanceInfo.approveName }}</span>
|
||||
<div class="flex items-center opacity-50">
|
||||
<span>XXXX有限公司</span>
|
||||
<Divider type="vertical" />
|
||||
<span>提交于: 2022-01-01 12:00:00</span>
|
||||
<span>提交于: {{ instanceInfo.createTime }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 右侧图标 -->
|
||||
<div class="z-100 absolute right-3 top-3">
|
||||
<img :src="RejectionPng" class="size-[96px]" />
|
||||
</div>
|
||||
</div>
|
||||
<Tabs class="flex-1">
|
||||
<TabPane key="1" tab="审批详情">
|
||||
<div class="h-fulloverflow-y-auto">
|
||||
<Alert message="该页面仅为静态页 后期可能会用到!" type="info" />
|
||||
<!-- <Alert message="该页面仅为静态页 后期可能会用到!" type="info" /> -->
|
||||
<iframe
|
||||
:src="`/workflow/leave-inner?readonly=true&id=${instanceInfo.businessId}`"
|
||||
class="h-[300px] w-full"
|
||||
></iframe>
|
||||
<Divider />
|
||||
<ApprovalTimeline :list="timeLine" />
|
||||
<ApprovalTimeline :list="currentInstance.list" />
|
||||
</div>
|
||||
</TabPane>
|
||||
<TabPane key="2" tab="审批记录">审批记录</TabPane>
|
||||
<TabPane key="3" tab="全文评论(999+)">全文评论</TabPane>
|
||||
<TabPane key="2" tab="审批流程图">
|
||||
<img
|
||||
:src="`data:image/png;base64,${currentInstance.image}`"
|
||||
class="rounded-lg border"
|
||||
/>
|
||||
</TabPane>
|
||||
</Tabs>
|
||||
</div>
|
||||
<!-- 固定底部 -->
|
||||
@ -155,6 +204,7 @@ function handleCardClick(id: string) {
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<Fallback v-else title="点击左侧选择" />
|
||||
</div>
|
||||
</Page>
|
||||
</template>
|
||||
|
@ -1,2 +1,3 @@
|
||||
export * from './components';
|
||||
export * from './ui';
|
||||
export { VbenAvatar } from '@vben-core/shadcn-ui';
|
||||
|
Loading…
Reference in New Issue
Block a user