chore: 我的待办 & 请假

This commit is contained in:
dap 2024-12-12 16:07:42 +08:00
parent 8f9215ffad
commit e6e2c84f45
16 changed files with 955 additions and 99 deletions

View 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}`,
);
}

View 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[];
}

View 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}`);
}

View 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;
}

View File

@ -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',
},
];
/**

View File

@ -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>

View File

@ -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

View 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}`);
}

View 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;
}

View 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',
},
];

View File

@ -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>

View 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>

View 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;
}
// getValuesreadonly
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>

View File

@ -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>

View File

@ -1,2 +1,3 @@
export * from './components';
export * from './ui';
export { VbenAvatar } from '@vben-core/shadcn-ui';