mirror of
https://gitee.com/dapppp/ruoyi-plus-vben5.git
synced 2026-04-05 23:03:14 +08:00
refactor: 使用映射方式替换iframe来显示流程详情
This commit is contained in:
@@ -21,7 +21,7 @@ export interface TaskInfo {
|
|||||||
permissionList?: any;
|
permissionList?: any;
|
||||||
userList?: any;
|
userList?: any;
|
||||||
formCustom: string;
|
formCustom: string;
|
||||||
formPath?: any;
|
formPath: string;
|
||||||
flowCode: string;
|
flowCode: string;
|
||||||
version: string;
|
version: string;
|
||||||
flowStatus: string;
|
flowStatus: string;
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import type { RouteRecordRaw } from 'vue-router';
|
|||||||
import { mergeRouteModules, traverseTreeValues } from '@vben/utils';
|
import { mergeRouteModules, traverseTreeValues } from '@vben/utils';
|
||||||
|
|
||||||
import { coreRoutes, fallbackNotFoundRoute } from './core';
|
import { coreRoutes, fallbackNotFoundRoute } from './core';
|
||||||
import { workflowIframeRoutes } from './workflow-iframe';
|
|
||||||
|
|
||||||
const dynamicRouteFiles = import.meta.glob('./modules/**/*.ts', {
|
const dynamicRouteFiles = import.meta.glob('./modules/**/*.ts', {
|
||||||
eager: true,
|
eager: true,
|
||||||
@@ -27,12 +26,11 @@ const externalRoutes: RouteRecordRaw[] = [];
|
|||||||
const routes: RouteRecordRaw[] = [
|
const routes: RouteRecordRaw[] = [
|
||||||
...coreRoutes,
|
...coreRoutes,
|
||||||
...externalRoutes,
|
...externalRoutes,
|
||||||
...workflowIframeRoutes,
|
|
||||||
fallbackNotFoundRoute,
|
fallbackNotFoundRoute,
|
||||||
];
|
];
|
||||||
|
|
||||||
/** 基本路由(登录, 第三方登录, 注册等) + workflowIframe路由不需要拦截 */
|
/** 基本路由(登录, 第三方登录, 注册等) */
|
||||||
const basicRoutes = [...coreRoutes, ...workflowIframeRoutes];
|
const basicRoutes = [...coreRoutes];
|
||||||
/** 基本路由列表,这些路由不需要进入权限拦截 */
|
/** 基本路由列表,这些路由不需要进入权限拦截 */
|
||||||
const coreRouteNames = traverseTreeValues(basicRoutes, (route) => route.name);
|
const coreRouteNames = traverseTreeValues(basicRoutes, (route) => route.name);
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
import type { RouteRecordRaw } from '@vben/types';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 该文件存放workflow表单的iframe内嵌路由
|
|
||||||
* 不需要权限认证 少走两个接口😅
|
|
||||||
*/
|
|
||||||
export const workflowIframeRoutes: RouteRecordRaw[] = [
|
|
||||||
// 这里是iframe使用的 去掉外层的BasicLayout
|
|
||||||
{
|
|
||||||
name: 'WorkflowLeaveInner',
|
|
||||||
path: '/workflow/leaveEdit/index/iframe',
|
|
||||||
component: () => import('#/views/workflow/leave/leave-form.vue'),
|
|
||||||
meta: {
|
|
||||||
hideInTab: true,
|
|
||||||
title: '请假申请',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
@@ -1,16 +1,17 @@
|
|||||||
<!--
|
<!--
|
||||||
审批详情
|
审批详情
|
||||||
约定${task.formPath}/frame 为内嵌表单 用于展示 需要在本地路由添加
|
动态渲染要显示的内容 需要再flowDescripionsMap先定义好组件
|
||||||
apps/web-antd/src/router/routes/workflow-iframe.ts
|
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import type { DescripionsMapKey } from '../register';
|
||||||
|
|
||||||
import type { FlowInfoResponse } from '#/api/workflow/instance/model';
|
import type { FlowInfoResponse } from '#/api/workflow/instance/model';
|
||||||
import type { TaskInfo } from '#/api/workflow/task/model';
|
import type { TaskInfo } from '#/api/workflow/task/model';
|
||||||
|
|
||||||
import { Divider, Skeleton } from 'ant-design-vue';
|
import { Divider } from 'ant-design-vue';
|
||||||
|
|
||||||
import { ApprovalTimeline } from '.';
|
import { ApprovalTimeline } from '.';
|
||||||
|
import { flowDescripionsMap } from '../register';
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'ApprovalDetails',
|
name: 'ApprovalDetails',
|
||||||
@@ -27,14 +28,14 @@ defineProps<{
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<!-- 约定${task.formPath}/frame 为内嵌表单 用于展示 需要在本地路由添加 -->
|
<!--
|
||||||
<iframe
|
动态渲染要显示的内容 需要再flowDescripionsMap先定义好组件
|
||||||
v-show="iframeLoaded"
|
business-id为业务ID 必传
|
||||||
:src="`${task.formPath}/iframe?readonly=true&id=${task.businessId}`"
|
-->
|
||||||
:style="{ height: `${iframeHeight}px` }"
|
<component
|
||||||
class="w-full"
|
:is="flowDescripionsMap[task.formPath as DescripionsMapKey]"
|
||||||
></iframe>
|
:business-id="task.businessId"
|
||||||
<Skeleton v-show="!iframeLoaded" :paragraph="{ rows: 6 }" active />
|
/>
|
||||||
<Divider />
|
<Divider />
|
||||||
<ApprovalTimeline :list="currentFlowInfo.list" />
|
<ApprovalTimeline :list="currentFlowInfo.list" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -96,9 +96,7 @@ export const columns: VxeGridProps['columns'] = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const modalSchema: (isEdit: boolean) => VbenFormSchema[] = (
|
export const modalSchema: () => VbenFormSchema[] = () => [
|
||||||
isEdit: boolean,
|
|
||||||
) => [
|
|
||||||
{
|
{
|
||||||
label: '主键',
|
label: '主键',
|
||||||
fieldName: 'id',
|
fieldName: 'id',
|
||||||
@@ -120,7 +118,6 @@ export const modalSchema: (isEdit: boolean) => VbenFormSchema[] = (
|
|||||||
defaultValue: 'leave1',
|
defaultValue: 'leave1',
|
||||||
rules: 'selectRequired',
|
rules: 'selectRequired',
|
||||||
dependencies: {
|
dependencies: {
|
||||||
show: () => isEdit,
|
|
||||||
triggerFields: [''],
|
triggerFields: [''],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { LeaveVO } from './api/model';
|
import type { LeaveVO } from '../leave/api/model';
|
||||||
|
|
||||||
import { computed } from 'vue';
|
import { computed, onMounted, shallowRef } from 'vue';
|
||||||
|
|
||||||
import { Descriptions, DescriptionsItem } from 'ant-design-vue';
|
import { Descriptions, DescriptionsItem, Skeleton } from 'ant-design-vue';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
|
import { leaveInfo } from './api';
|
||||||
import { leaveTypeOptions } from './data';
|
import { leaveTypeOptions } from './data';
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
@@ -13,11 +14,17 @@ defineOptions({
|
|||||||
inheritAttrs: false,
|
inheritAttrs: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const props = defineProps<{ data: LeaveVO }>();
|
const props = defineProps<{ businessId: number | string }>();
|
||||||
|
|
||||||
|
const data = shallowRef<LeaveVO>();
|
||||||
|
onMounted(async () => {
|
||||||
|
const resp = await leaveInfo(props.businessId);
|
||||||
|
data.value = resp;
|
||||||
|
});
|
||||||
|
|
||||||
const leaveType = computed(() => {
|
const leaveType = computed(() => {
|
||||||
return (
|
return (
|
||||||
leaveTypeOptions.find((item) => item.value === props.data.leaveType)
|
leaveTypeOptions.find((item) => item.value === data.value?.leaveType)
|
||||||
?.label ?? '未知'
|
?.label ?? '未知'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -28,18 +35,22 @@ function formatDate(date: string) {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Descriptions :column="1" size="middle">
|
<div class="rounded-[6px] border p-2">
|
||||||
<DescriptionsItem label="请假类型">
|
<Descriptions v-if="data" :column="1" size="middle">
|
||||||
{{ leaveType }}
|
<DescriptionsItem label="请假类型">
|
||||||
</DescriptionsItem>
|
{{ leaveType }}
|
||||||
<DescriptionsItem label="请假时间">
|
</DescriptionsItem>
|
||||||
{{ formatDate(data.startDate) }} - {{ formatDate(data.endDate) }}
|
<DescriptionsItem label="请假时间">
|
||||||
</DescriptionsItem>
|
{{ formatDate(data.startDate) }} - {{ formatDate(data.endDate) }}
|
||||||
<DescriptionsItem label="请假时长">
|
</DescriptionsItem>
|
||||||
{{ data.leaveDays }}天
|
<DescriptionsItem label="请假时长">
|
||||||
</DescriptionsItem>
|
{{ data.leaveDays }}天
|
||||||
<DescriptionsItem label="请假原因">
|
</DescriptionsItem>
|
||||||
{{ data.remark || '无' }}
|
<DescriptionsItem label="请假原因">
|
||||||
</DescriptionsItem>
|
{{ data.remark || '无' }}
|
||||||
</Descriptions>
|
</DescriptionsItem>
|
||||||
|
</Descriptions>
|
||||||
|
|
||||||
|
<Skeleton active v-else />
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { LeaveVO } from './api/model';
|
|
||||||
|
|
||||||
import type { StartWorkFlowReqData } from '#/api/workflow/task/model';
|
import type { StartWorkFlowReqData } from '#/api/workflow/task/model';
|
||||||
|
|
||||||
import { computed, onMounted, ref, useTemplateRef } from 'vue';
|
import { onMounted } from 'vue';
|
||||||
import { useRoute, useRouter } from 'vue-router';
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
|
|
||||||
import { useVbenModal } from '@vben/common-ui';
|
import { useVbenModal } from '@vben/common-ui';
|
||||||
@@ -18,19 +16,10 @@ import { startWorkFlow } from '#/api/workflow/task';
|
|||||||
import { applyModal } from '../components';
|
import { applyModal } from '../components';
|
||||||
import { leaveAdd, leaveInfo, leaveUpdate } from './api';
|
import { leaveAdd, leaveInfo, leaveUpdate } from './api';
|
||||||
import { modalSchema } from './data';
|
import { modalSchema } from './data';
|
||||||
import LeaveDescription from './leave-description.vue';
|
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const readonly = route.query?.readonly === 'true';
|
|
||||||
const id = route.query?.id as string;
|
const id = route.query?.id as string;
|
||||||
|
|
||||||
/**
|
|
||||||
* id存在&readonly时候
|
|
||||||
*/
|
|
||||||
const showActionBtn = computed(() => {
|
|
||||||
return !readonly;
|
|
||||||
});
|
|
||||||
|
|
||||||
const [BasicForm, formApi] = useVbenForm({
|
const [BasicForm, formApi] = useVbenForm({
|
||||||
commonConfig: {
|
commonConfig: {
|
||||||
// 默认占满两列
|
// 默认占满两列
|
||||||
@@ -40,45 +29,20 @@ const [BasicForm, formApi] = useVbenForm({
|
|||||||
// 通用配置项 会影响到所有表单项
|
// 通用配置项 会影响到所有表单项
|
||||||
componentProps: {
|
componentProps: {
|
||||||
class: 'w-full',
|
class: 'w-full',
|
||||||
disabled: readonly,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
schema: modalSchema(!readonly),
|
schema: modalSchema(),
|
||||||
showDefaultActions: false,
|
showDefaultActions: false,
|
||||||
wrapperClass: 'grid-cols-2',
|
wrapperClass: 'grid-cols-2',
|
||||||
});
|
});
|
||||||
|
|
||||||
const leaveDescription = ref<LeaveVO>();
|
|
||||||
const showDescription = computed(() => {
|
|
||||||
return readonly && leaveDescription.value;
|
|
||||||
});
|
|
||||||
const cardRef = useTemplateRef('cardRef');
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
// 只读 获取信息赋值
|
// 只读 获取信息赋值
|
||||||
if (id) {
|
if (id) {
|
||||||
const resp = await leaveInfo(id);
|
const resp = await leaveInfo(id);
|
||||||
leaveDescription.value = resp;
|
|
||||||
await formApi.setValues(resp);
|
await formApi.setValues(resp);
|
||||||
const dateRange = [dayjs(resp.startDate), dayjs(resp.endDate)];
|
const dateRange = [dayjs(resp.startDate), dayjs(resp.endDate)];
|
||||||
await formApi.setFieldValue('dateRange', dateRange);
|
await formApi.setFieldValue('dateRange', dateRange);
|
||||||
|
|
||||||
/**
|
|
||||||
* window.parent(最近的上一级父页面)
|
|
||||||
* 主要解决内嵌iframe卡顿的问题
|
|
||||||
*/
|
|
||||||
if (readonly) {
|
|
||||||
// 渲染完毕才显示表单
|
|
||||||
window.parent.postMessage({ type: 'mounted' }, '*');
|
|
||||||
// 获取表单高度 内嵌时保持一致
|
|
||||||
setTimeout(() => {
|
|
||||||
const el = cardRef.value?.$el as HTMLDivElement;
|
|
||||||
// 获取高度
|
|
||||||
const height = el?.offsetHeight ?? 0;
|
|
||||||
if (height) {
|
|
||||||
window.parent.postMessage({ type: 'height', height }, '*');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -156,22 +120,14 @@ function handleComplete() {
|
|||||||
formApi.resetForm();
|
formApi.resetForm();
|
||||||
router.push('/demo/leave');
|
router.push('/demo/leave');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 显示详情时 需要较小的padding
|
|
||||||
*/
|
|
||||||
const cardSize = computed(() => {
|
|
||||||
return showDescription.value ? 'small' : 'default';
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Card ref="cardRef" :size="cardSize">
|
<Card>
|
||||||
<div id="leave-form">
|
<div id="leave-form">
|
||||||
<!-- 使用v-if会影响生命周期 -->
|
<!-- 使用v-if会影响生命周期 -->
|
||||||
<BasicForm v-show="!showDescription" />
|
<BasicForm />
|
||||||
<LeaveDescription v-if="showDescription" :data="leaveDescription!" />
|
<div class="flex justify-end gap-2">
|
||||||
<div v-if="showActionBtn" class="flex justify-end gap-2">
|
|
||||||
<a-button @click="handleTempSave">暂存</a-button>
|
<a-button @click="handleTempSave">暂存</a-button>
|
||||||
<a-button type="primary" @click="handleStartWorkFlow">提交</a-button>
|
<a-button type="primary" @click="handleStartWorkFlow">提交</a-button>
|
||||||
</div>
|
</div>
|
||||||
@@ -179,14 +135,3 @@ const cardSize = computed(() => {
|
|||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
html:has(#leave-form) {
|
|
||||||
/**
|
|
||||||
去除顶部进度条样式
|
|
||||||
*/
|
|
||||||
#nprogress {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
21
apps/web-antd/src/views/workflow/register.ts
Normal file
21
apps/web-antd/src/views/workflow/register.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { defineAsyncComponent, markRaw } from 'vue';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 这里定义流程描述组件
|
||||||
|
*/
|
||||||
|
|
||||||
|
const LeaveDescription = defineAsyncComponent(
|
||||||
|
() => import('#/views/workflow/leave/leave-description.vue'),
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* key为流程的路径(task.formPath) value为要显示的组件
|
||||||
|
*/
|
||||||
|
export const flowDescripionsMap = {
|
||||||
|
/**
|
||||||
|
* 请假申请 详情
|
||||||
|
*/
|
||||||
|
'/workflow/leaveEdit/index': markRaw(LeaveDescription),
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DescripionsMapKey = keyof typeof flowDescripionsMap;
|
||||||
Reference in New Issue
Block a user