refactor(projects): 修改流程实例动态引入组件

This commit is contained in:
AN
2025-06-21 10:50:48 +08:00
parent 50e7b5158d
commit a3dcee4a11
7 changed files with 117 additions and 83 deletions

View File

@ -1,13 +1,14 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, reactive, ref, watch } from 'vue'; import { computed, onMounted, reactive, ref, watch } from 'vue';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { useBoolean } from '@sa/hooks'; import { useBoolean, useLoading } from '@sa/hooks';
import { flowCodeTypeOptions, flowCodeTypeRecord, leaveTypeOptions, leaveTypeRecord } from '@/constants/workflow'; import { flowCodeTypeOptions, flowCodeTypeRecord, leaveTypeOptions, leaveTypeRecord } from '@/constants/workflow';
import { fetchCreateLeave, fetchGetLeaveDetail, fetchStartWorkflow, fetchUpdateLeave } from '@/service/api/workflow'; import { fetchCreateLeave, fetchGetLeaveDetail, fetchStartWorkflow, fetchUpdateLeave } from '@/service/api/workflow';
import { useFormRules, useNaiveForm } from '@/hooks/common/form'; import { useFormRules, useNaiveForm } from '@/hooks/common/form';
import { useDict } from '@/hooks/business/dict'; import { useDict } from '@/hooks/business/dict';
import { $t } from '@/locales'; import { $t } from '@/locales';
import ApprovalInfoPanel from '@/components/custom/work-flow/approval-info-panel.vue'; import ApprovalInfoPanel from '@/components/custom/workflow/approval-info-panel.vue';
import WorkflowTaskApplyModal from '@/components/custom/workflow/workflow-task-apply-modal.vue';
defineOptions({ defineOptions({
name: 'LeaveEdit' name: 'LeaveEdit'
@ -40,9 +41,10 @@ const visible = defineModel<boolean>('visible', {
const approvalInfoPanelRef = ref<InstanceType<typeof ApprovalInfoPanel>>(); const approvalInfoPanelRef = ref<InstanceType<typeof ApprovalInfoPanel>>();
const { bool: taskApplyVisible, setTrue: setTaskApplyVisible } = useBoolean(); const { bool: taskApplyVisible, setTrue: setTaskApplyVisible } = useBoolean();
const { loading, startLoading, endLoading } = useLoading();
const { formRef, validate, restoreValidation } = useNaiveForm(); const { formRef, validate, restoreValidation } = useNaiveForm();
const { createRequiredRule } = useFormRules(); const { createRequiredRule } = useFormRules();
const title = computed(() => { const title = computed(() => {
const titles: Record<CommonType.WorkflowTableOperateType, string> = { const titles: Record<CommonType.WorkflowTableOperateType, string> = {
add: '新增请假申请', add: '新增请假申请',
@ -212,65 +214,75 @@ function handleTaskFinished() {
emit('submitted'); emit('submitted');
} }
watch(visible, async () => { async function initializeData() {
if (visible.value) { if (visible.value) {
startLoading();
await handleUpdateModelWhenEdit(); await handleUpdateModelWhenEdit();
restoreValidation(); restoreValidation();
if (showApprovalInfoPanel.value) { if (showApprovalInfoPanel.value) {
approvalInfoPanelRef.value?.initData(); approvalInfoPanelRef.value?.initData();
} }
endLoading();
} }
}); }
watch(visible, initializeData);
onMounted(initializeData);
</script> </script>
<template> <template>
<NDrawer v-model:show="visible" :title="title" display-directive="show" :width="1100" class="max-w-90%"> <NDrawer v-model:show="visible" :title="title" display-directive="show" :width="1100" class="max-w-90%">
<NDrawerContent :title="title" :native-scrollbar="false" closable> <NDrawerContent :title="title" :native-scrollbar="false" closable>
<div v-if="!readonly"> <NSpin :show="loading">
<NForm ref="formRef" :model="model" :rules="rules"> <div :class="loading ? 'hidden' : ''">
<NFormItem label="流程类型" path="flowCode"> <div v-if="!readonly" class="h-full">
<NSelect v-model:value="model.flowCode" placeholder="请输入流程类型" :options="flowCodeTypeOptions" /> <NForm ref="formRef" :model="model" :rules="rules">
</NFormItem> <NFormItem label="流程类型" path="flowCode">
<NFormItem label="请类型" path="leaveType"> <NSelect v-model:value="model.flowCode" placeholder="请输入流程类型" :options="flowCodeTypeOptions" />
<NSelect v-model:value="model.leaveType" placeholder="请输入请假类型" :options="leaveTypeOptions" /> </NFormItem>
</NFormItem> <NFormItem label="请假类型" path="leaveType">
<NFormItem label="请假时间" path="startDate"> <NSelect v-model:value="model.leaveType" placeholder="请输入请假类型" :options="leaveTypeOptions" />
<NDatePicker </NFormItem>
v-model:formatted-value="dateRange" <NFormItem label="请假时间" path="startDate">
class="w-full" <NDatePicker
type="daterange" v-model:formatted-value="dateRange"
value-format="yyyy-MM-dd" class="w-full"
clearable type="daterange"
/> value-format="yyyy-MM-dd"
</NFormItem> clearable
<NFormItem label="请假天数" path="leaveDays"> />
<NInputNumber v-model:value="model.leaveDays" class="w-full" disabled placeholder="请输入请假天数" /> </NFormItem>
</NFormItem> <NFormItem label="请假天数" path="leaveDays">
<NFormItem label="请假原因" path="remark"> <NInputNumber v-model:value="model.leaveDays" class="w-full" disabled placeholder="请输入请假天数" />
<NInput v-model:value="model.remark" placeholder="请输入请假原因" /> </NFormItem>
</NFormItem> <NFormItem label="请假原因" path="remark">
</NForm> <NInput v-model:value="model.remark" placeholder="请输入请假原因" />
</div> </NFormItem>
<div v-else> </NForm>
<NDescriptions bordered :column="2" label-placement="left"> </div>
<NDescriptionsItem label="流程类型"> <div v-else>
{{ flowCodeTypeRecord[modelDetail.flowCode] }} <NDescriptions bordered :column="2" label-placement="left">
</NDescriptionsItem> <NDescriptionsItem label="流程类型">
<NDescriptionsItem label="请假类型"> {{ flowCodeTypeRecord[modelDetail.flowCode] }}
<NTag type="info">{{ leaveTypeRecord[modelDetail.leaveType!] }}</NTag> </NDescriptionsItem>
</NDescriptionsItem> <NDescriptionsItem label="请假类型">
<NDescriptionsItem label="请假时间"> <NTag type="info">{{ leaveTypeRecord[modelDetail.leaveType!] }}</NTag>
{{ `${modelDetail.startDate}${modelDetail.endDate}` }} </NDescriptionsItem>
</NDescriptionsItem> <NDescriptionsItem label="请假时间">
<NDescriptionsItem label="请假天数">{{ modelDetail.leaveDays }} </NDescriptionsItem> {{ `${modelDetail.startDate}${modelDetail.endDate}` }}
<NDescriptionsItem label="请假原因"> </NDescriptionsItem>
{{ modelDetail.remark || '-' }} <NDescriptionsItem label="请假天数">{{ modelDetail.leaveDays }} </NDescriptionsItem>
</NDescriptionsItem> <NDescriptionsItem label="请假原因">
</NDescriptions> {{ modelDetail.remark || '-' }}
<!-- 审批信息 --> </NDescriptionsItem>
<ApprovalInfoPanel v-if="showApprovalInfoPanel" ref="approvalInfoPanelRef" :business-id="modelDetail.id!" /> </NDescriptions>
</div> <!-- 审批信息 -->
<ApprovalInfoPanel v-if="showApprovalInfoPanel" ref="approvalInfoPanelRef" :business-id="modelDetail.id!" />
</div>
</div>
</NSpin>
<template #footer> <template #footer>
<div v-if="!readonly"> <div v-if="!readonly">
<NSpace :size="16"> <NSpace :size="16">

View File

@ -9,7 +9,7 @@ export {}
declare module 'vue' { declare module 'vue' {
export interface GlobalComponents { export interface GlobalComponents {
AppProvider: typeof import('./../components/common/app-provider.vue')['default'] AppProvider: typeof import('./../components/common/app-provider.vue')['default']
ApprovalInfoPanel: typeof import('./../components/custom/work-flow/approval-info-panel.vue')['default'] ApprovalInfoPanel: typeof import('./../components/custom/workflow/approval-info-panel.vue')['default']
BetterScroll: typeof import('./../components/custom/better-scroll.vue')['default'] BetterScroll: typeof import('./../components/custom/better-scroll.vue')['default']
BooleanTag: typeof import('./../components/custom/boolean-tag.vue')['default'] BooleanTag: typeof import('./../components/custom/boolean-tag.vue')['default']
ButtonIcon: typeof import('./../components/custom/button-icon.vue')['default'] ButtonIcon: typeof import('./../components/custom/button-icon.vue')['default']
@ -60,7 +60,7 @@ declare module 'vue' {
IconUilSearch: typeof import('~icons/uil/search')['default'] IconUilSearch: typeof import('~icons/uil/search')['default']
JsonPreview: typeof import('./../components/custom/json-preview.vue')['default'] JsonPreview: typeof import('./../components/custom/json-preview.vue')['default']
LangSwitch: typeof import('./../components/common/lang-switch.vue')['default'] LangSwitch: typeof import('./../components/common/lang-switch.vue')['default']
LeaveEdit: typeof import('./../components/custom/work-flow/leave-edit/index.vue')['default'] LeaveEdit: typeof import('./../components/custom/workflow/leave-edit/index.vue')['default']
LeaveForm: typeof import('../components/custom/workflow-leave-form.vue')['default'] LeaveForm: typeof import('../components/custom/workflow-leave-form.vue')['default']
LookForward: typeof import('./../components/custom/look-forward.vue')['default'] LookForward: typeof import('./../components/custom/look-forward.vue')['default']
MenuToggler: typeof import('./../components/common/menu-toggler.vue')['default'] MenuToggler: typeof import('./../components/common/menu-toggler.vue')['default']
@ -76,6 +76,7 @@ declare module 'vue' {
NButton: typeof import('naive-ui')['NButton'] NButton: typeof import('naive-ui')['NButton']
NCard: typeof import('naive-ui')['NCard'] NCard: typeof import('naive-ui')['NCard']
NCheckbox: typeof import('naive-ui')['NCheckbox'] NCheckbox: typeof import('naive-ui')['NCheckbox']
NCheckboxGroup: typeof import('naive-ui')['NCheckboxGroup']
NCode: typeof import('naive-ui')['NCode'] NCode: typeof import('naive-ui')['NCode']
NCollapse: typeof import('naive-ui')['NCollapse'] NCollapse: typeof import('naive-ui')['NCollapse']
NCollapseItem: typeof import('naive-ui')['NCollapseItem'] NCollapseItem: typeof import('naive-ui')['NCollapseItem']
@ -123,6 +124,7 @@ declare module 'vue' {
NRadioGroup: typeof import('naive-ui')['NRadioGroup'] NRadioGroup: typeof import('naive-ui')['NRadioGroup']
NScrollbar: typeof import('naive-ui')['NScrollbar'] NScrollbar: typeof import('naive-ui')['NScrollbar']
NSelect: typeof import('naive-ui')['NSelect'] NSelect: typeof import('naive-ui')['NSelect']
NSkeleton: typeof import('naive-ui')['NSkeleton']
NSpace: typeof import('naive-ui')['NSpace'] NSpace: typeof import('naive-ui')['NSpace']
NSpin: typeof import('naive-ui')['NSpin'] NSpin: typeof import('naive-ui')['NSpin']
NStatistic: typeof import('naive-ui')['NStatistic'] NStatistic: typeof import('naive-ui')['NStatistic']
@ -162,7 +164,7 @@ declare module 'vue' {
TinymceEditor: typeof import('./../components/custom/tinymce-editor.vue')['default'] TinymceEditor: typeof import('./../components/custom/tinymce-editor.vue')['default']
UserSelect: typeof import('./../components/custom/user-select.vue')['default'] UserSelect: typeof import('./../components/custom/user-select.vue')['default']
WaveBg: typeof import('./../components/custom/wave-bg.vue')['default'] WaveBg: typeof import('./../components/custom/wave-bg.vue')['default']
WorkflowCategorySelect: typeof import('./../components/custom/work-flow/workflow-category-select.vue')['default'] WorkflowCategorySelect: typeof import('./../components/custom/workflow/workflow-category-select.vue')['default']
WorkflowTaskApplyModal: typeof import('./../components/custom/work-flow/workflow-task-apply-modal.vue')['default'] WorkflowTaskApplyModal: typeof import('./../components/custom/workflow/workflow-task-apply-modal.vue')['default']
} }
} }

View File

@ -12,7 +12,7 @@ import { useDict } from '@/hooks/business/dict';
import { $t } from '@/locales'; import { $t } from '@/locales';
import DictTag from '@/components/custom/dict-tag.vue'; import DictTag from '@/components/custom/dict-tag.vue';
import ButtonIcon from '@/components/custom/button-icon.vue'; import ButtonIcon from '@/components/custom/button-icon.vue';
import LeaveEdit from '@/components/custom/work-flow/leave-edit/index.vue'; import LeaveEdit from '@/components/custom/workflow/leave-edit/index.vue';
import LeaveSearch from './modules/leave-search.vue'; import LeaveSearch from './modules/leave-search.vue';
defineOptions({ defineOptions({

View File

@ -1,5 +1,6 @@
<script setup lang="tsx"> <script setup lang="tsx">
import { computed, reactive, ref, watch } from 'vue'; import type { AsyncComponentLoader } from 'vue';
import { computed, defineAsyncComponent, markRaw, reactive, ref, shallowRef, watch } from 'vue';
import { NButton, NDivider, NEmpty, NInput, NRadioButton, NRadioGroup, NSpin, NTag } from 'naive-ui'; import { NButton, NDivider, NEmpty, NInput, NRadioButton, NRadioGroup, NSpin, NTag } from 'naive-ui';
import { useBoolean, useLoading } from '@sa/hooks'; import { useBoolean, useLoading } from '@sa/hooks';
import { workflowActivityStatusRecord } from '@/constants/workflow'; import { workflowActivityStatusRecord } from '@/constants/workflow';
@ -13,12 +14,15 @@ import {
import { useAppStore } from '@/store/modules/app'; import { useAppStore } from '@/store/modules/app';
import { useTable, useTableOperate } from '@/hooks/common/table'; import { useTable, useTableOperate } from '@/hooks/common/table';
import { useDict } from '@/hooks/business/dict'; import { useDict } from '@/hooks/business/dict';
import { humpToLine } from '@/utils/common';
import DictTag from '@/components/custom/dict-tag.vue'; import DictTag from '@/components/custom/dict-tag.vue';
import { $t } from '@/locales'; import { $t } from '@/locales';
import ButtonIcon from '@/components/custom/button-icon.vue'; import ButtonIcon from '@/components/custom/button-icon.vue';
import ProcessInstanceSearch from './modules/process-instance-search.vue'; import ProcessInstanceSearch from './modules/process-instance-search.vue';
import ProcessInstanceVariableDrawer from './modules/process-instance-variable-drawer.vue'; import ProcessInstanceVariableDrawer from './modules/process-instance-variable-drawer.vue';
const dynamicComponent = shallowRef();
interface RunningStatusOption { interface RunningStatusOption {
label: string; label: string;
value: boolean; value: boolean;
@ -32,7 +36,7 @@ useDict('wf_business_status');
const appStore = useAppStore(); const appStore = useAppStore();
const { bool: variableVisible, setTrue: showVariableDrawer } = useBoolean(false); const { bool: variableVisible, setTrue: showVariableDrawer } = useBoolean(false);
const { bool: leaveEditVisible, setTrue: showLeaveEditDrawer } = useBoolean(false); const { bool: previewVisible, setTrue: showPreviewDrawer } = useBoolean(false);
const runningStatus = ref<boolean>(true); const runningStatus = ref<boolean>(true);
const runningStatusOptions = ref<RunningStatusOption[]>([ const runningStatusOptions = ref<RunningStatusOption[]>([
@ -155,7 +159,25 @@ const operateColumns = ref<NaiveUI.TableColumn<Api.Workflow.ProcessInstance>[]>(
const id = row.id; const id = row.id;
const showAll = runningStatus.value; const showAll = runningStatus.value;
const buttons = []; const buttons = [];
buttons.push(
<ButtonIcon
text
type="info"
icon="material-symbols:visibility-outline"
tooltipContent="流程预览"
onClick={() => handlePreview(row)}
/>
);
buttons.push(
<ButtonIcon
text
type="info"
icon="material-symbols:variable-insert"
tooltipContent="流程变量"
onClick={() => handleShowVariable(id)}
/>
);
if (showAll) { if (showAll) {
buttons.push( buttons.push(
<ButtonIcon <ButtonIcon
@ -171,7 +193,6 @@ const operateColumns = ref<NaiveUI.TableColumn<Api.Workflow.ProcessInstance>[]>(
/> />
); );
} }
if (showAll) { if (showAll) {
buttons.push( buttons.push(
<ButtonIcon <ButtonIcon
@ -185,26 +206,6 @@ const operateColumns = ref<NaiveUI.TableColumn<Api.Workflow.ProcessInstance>[]>(
); );
} }
buttons.push(
<ButtonIcon
text
type="info"
icon="material-symbols:visibility-outline"
tooltipContent="流程预览"
onClick={() => handleShowLeaveEdit(row.businessId)}
/>
);
buttons.push(
<ButtonIcon
text
type="info"
icon="material-symbols:variable-insert"
tooltipContent="流程变量"
onClick={() => handleShowVariable(id)}
/>
);
const buttonWithDividers = buttons.flatMap((btn, index) => { const buttonWithDividers = buttons.flatMap((btn, index) => {
if (index === 0) return [btn]; if (index === 0) return [btn];
return [<NDivider vertical />, btn]; return [<NDivider vertical />, btn];
@ -318,10 +319,29 @@ async function handleShowVariable(id: CommonType.IdType) {
showVariableDrawer(); showVariableDrawer();
} }
const leaveEditBusinessId = ref<CommonType.IdType>(); const modules = import.meta.glob('@/components/custom/workflow/**/*.vue');
async function handleShowLeaveEdit(businessId: CommonType.IdType) { const businessId = ref<CommonType.IdType>();
leaveEditBusinessId.value = businessId;
showLeaveEditDrawer(); async function handlePreview(row: Api.Workflow.ProcessInstance) {
businessId.value = row.businessId;
const formPath = row.formPath;
if (formPath) {
const url = `/src/components/custom${humpToLine(formPath)}.vue`;
const loader = modules[url];
if (!loader) {
window.$message?.error('组件不存在');
return;
}
dynamicComponent.value = markRaw(
defineAsyncComponent({
loader: async () => (await modules[url]()) as AsyncComponentLoader<any>,
delay: 200,
timeout: 3000
})
);
}
showPreviewDrawer();
} }
</script> </script>
@ -400,7 +420,7 @@ async function handleShowLeaveEdit(businessId: CommonType.IdType) {
:pagination="mobilePagination" :pagination="mobilePagination"
class="sm:h-full" class="sm:h-full"
/> />
<LeaveEdit v-model:visible="leaveEditVisible" operate-type="detail" :business-id="leaveEditBusinessId" /> <component :is="dynamicComponent" :visible="previewVisible" operate-type="detail" :business-id="businessId" />
<ProcessInstanceVariableDrawer v-model:visible="variableVisible" :row-data="editingData" /> <ProcessInstanceVariableDrawer v-model:visible="variableVisible" :row-data="editingData" />
</NCard> </NCard>
</div> </div>