feat(components): 增强审批信息面板,优化附件处理
This commit is contained in:
parent
496ed978ca
commit
49224afe2d
@ -1,20 +1,31 @@
|
|||||||
<script setup lang="tsx">
|
<script setup lang="tsx">
|
||||||
import { onMounted, ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import type { DataTableColumns } from 'naive-ui';
|
import type { DataTableColumns } from 'naive-ui';
|
||||||
import { NTag } from 'naive-ui';
|
import { NPopover, NSpace, NTag } from 'naive-ui';
|
||||||
|
import { useLoading } from '@sa/hooks';
|
||||||
import { fetchGetFlowHisTaskList } from '@/service/api/workflow/instance';
|
import { fetchGetFlowHisTaskList } from '@/service/api/workflow/instance';
|
||||||
|
import { fetchGetOssListByIds } from '@/service/api/system/oss';
|
||||||
import { useDict } from '@/hooks/business/dict';
|
import { useDict } from '@/hooks/business/dict';
|
||||||
|
import { useDownload } from '@/hooks/business/download';
|
||||||
import DictTag from '@/components/custom/dict-tag.vue';
|
import DictTag from '@/components/custom/dict-tag.vue';
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: 'ApprovalInfoPanel'
|
||||||
|
});
|
||||||
interface Props {
|
interface Props {
|
||||||
/** 业务id */
|
/** 业务id */
|
||||||
businessId: CommonType.IdType;
|
businessId: CommonType.IdType;
|
||||||
}
|
}
|
||||||
useDict('wf_task_status');
|
|
||||||
const props = defineProps<Props>();
|
const props = defineProps<Props>();
|
||||||
|
|
||||||
|
useDict('wf_task_status');
|
||||||
|
|
||||||
const activeTab = ref('info');
|
const activeTab = ref('info');
|
||||||
|
|
||||||
|
const { loading, startLoading, endLoading } = useLoading();
|
||||||
|
|
||||||
|
const { oss } = useDownload();
|
||||||
|
|
||||||
const columns = ref<DataTableColumns<Api.Workflow.HisTask>>([
|
const columns = ref<DataTableColumns<Api.Workflow.HisTask>>([
|
||||||
{
|
{
|
||||||
title: '任务名称',
|
title: '任务名称',
|
||||||
@ -28,10 +39,36 @@ const columns = ref<DataTableColumns<Api.Workflow.HisTask>>([
|
|||||||
align: 'center',
|
align: 'center',
|
||||||
width: 100,
|
width: 100,
|
||||||
render: row => {
|
render: row => {
|
||||||
|
if (!row.approveName) return null;
|
||||||
|
|
||||||
|
const approveNames = row.approveName.split(',');
|
||||||
|
|
||||||
|
if (approveNames.length <= 1) {
|
||||||
|
return (
|
||||||
|
<NTag size="small" type="info">
|
||||||
|
{row.approveName}
|
||||||
|
</NTag>
|
||||||
|
);
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<NTag size="small" type="info">
|
<NPopover trigger="hover" placement="bottom">
|
||||||
{row.approveName}
|
{{
|
||||||
</NTag>
|
trigger: () => (
|
||||||
|
<NTag size="small" type="info" class="cursor-pointer">
|
||||||
|
{approveNames[0]}...({approveNames.length})
|
||||||
|
</NTag>
|
||||||
|
),
|
||||||
|
default: () => (
|
||||||
|
<NSpace vertical size="small">
|
||||||
|
{approveNames.map(name => (
|
||||||
|
<NTag key={name} size="small" type="info">
|
||||||
|
{name}
|
||||||
|
</NTag>
|
||||||
|
))}
|
||||||
|
</NSpace>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</NPopover>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -54,13 +91,13 @@ const columns = ref<DataTableColumns<Api.Workflow.HisTask>>([
|
|||||||
title: '开始时间',
|
title: '开始时间',
|
||||||
key: 'createTime',
|
key: 'createTime',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
width: 100
|
width: 120
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '结束时间',
|
title: '结束时间',
|
||||||
key: 'updateTime',
|
key: 'updateTime',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
width: 100
|
width: 120
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '运行时间',
|
title: '运行时间',
|
||||||
@ -72,25 +109,86 @@ const columns = ref<DataTableColumns<Api.Workflow.HisTask>>([
|
|||||||
title: '附件',
|
title: '附件',
|
||||||
key: 'attachmentList',
|
key: 'attachmentList',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
width: 100
|
width: 120,
|
||||||
|
render: row => {
|
||||||
|
if (!row.attachmentList || row.attachmentList.length === 0) return null;
|
||||||
|
|
||||||
|
if (row.attachmentList.length === 1) {
|
||||||
|
return (
|
||||||
|
<NTag size="small" type="info" class="cursor-pointer">
|
||||||
|
<div class="flex items-center gap-2" onClick={() => oss(row.attachmentList[0].ossId)}>
|
||||||
|
{row.attachmentList[0].originalName}
|
||||||
|
</div>
|
||||||
|
</NTag>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<NPopover trigger="hover" placement="bottom">
|
||||||
|
{{
|
||||||
|
trigger: () => (
|
||||||
|
<NTag size="small" type="info" class="cursor-pointer">
|
||||||
|
{row.attachmentList[0].originalName}...({row.attachmentList.length})
|
||||||
|
</NTag>
|
||||||
|
),
|
||||||
|
default: () => (
|
||||||
|
<NSpace vertical size="small">
|
||||||
|
{row.attachmentList.map(item => (
|
||||||
|
<NTag key={item.ossId} size="small" type="info" class="cursor-pointer">
|
||||||
|
<div class="flex items-center gap-2" onClick={() => oss(item.ossId)}>
|
||||||
|
{item.originalName}
|
||||||
|
</div>
|
||||||
|
</NTag>
|
||||||
|
))}
|
||||||
|
</NSpace>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</NPopover>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const instanceId = ref<CommonType.IdType>();
|
const instanceId = ref<CommonType.IdType>();
|
||||||
|
|
||||||
const HisTask = ref<Api.Workflow.HisTask[]>([]);
|
const hisTask = ref<Api.Workflow.HisTask[]>([]);
|
||||||
|
|
||||||
|
/** 初始化数据 */
|
||||||
|
async function initData() {
|
||||||
|
hisTask.value = [];
|
||||||
|
if (loading.value) return;
|
||||||
|
startLoading();
|
||||||
|
try {
|
||||||
|
await getData();
|
||||||
|
} finally {
|
||||||
|
endLoading();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function getData() {
|
async function getData() {
|
||||||
const { error, data } = await fetchGetFlowHisTaskList(props.businessId);
|
const { error, data } = await fetchGetFlowHisTaskList(props.businessId);
|
||||||
if (error) {
|
if (error) {
|
||||||
window.$message?.error(error.message);
|
window.$message?.error(error.message);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
instanceId.value = data?.instanceId || '';
|
instanceId.value = data?.instanceId || '';
|
||||||
HisTask.value = data?.list || [];
|
hisTask.value = data?.list || [];
|
||||||
|
|
||||||
|
// 并行加载所有附件数据
|
||||||
|
const promises = hisTask.value.map(async item => {
|
||||||
|
if (item.ext) {
|
||||||
|
const { error: err, data: ossList } = await fetchGetOssListByIds(item.ext.split(','));
|
||||||
|
if (!err) {
|
||||||
|
item.attachmentList = ossList;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await Promise.all(promises);
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
defineExpose({
|
||||||
getData();
|
initData
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -99,14 +197,21 @@ onMounted(() => {
|
|||||||
<div>
|
<div>
|
||||||
<NTabs v-model:value="activeTab" type="segment" animated>
|
<NTabs v-model:value="activeTab" type="segment" animated>
|
||||||
<NTabPane bar-width="100px" name="info" tab="审批信息">
|
<NTabPane bar-width="100px" name="info" tab="审批信息">
|
||||||
<NDataTable class="max-h-500px" :columns="columns" :data="HisTask" />
|
<NDataTable
|
||||||
|
size="small"
|
||||||
|
:scroll-x="760"
|
||||||
|
class="max-h-500px"
|
||||||
|
:columns="columns"
|
||||||
|
:data="hisTask"
|
||||||
|
:loading="loading"
|
||||||
|
/>
|
||||||
</NTabPane>
|
</NTabPane>
|
||||||
<NTabPane bar-width="100px" name="image" tab="流程图">
|
<NTabPane bar-width="100px" name="image" tab="流程图">
|
||||||
“威尔!着火了!快来帮忙!”我听到女朋友大喊。现在一个难题在我面前——是恢复一个重要的 Amazon 服务,还是救公寓的火。
|
"威尔!着火了!快来帮忙!"我听到女朋友大喊。现在一个难题在我面前——是恢复一个重要的 Amazon 服务,还是救公寓的火。
|
||||||
<br />
|
<br />
|
||||||
<br />
|
<br />
|
||||||
我的脑海中忽然出现了 Amazon
|
我的脑海中忽然出现了 Amazon
|
||||||
著名的领导力准则”客户至上“,有很多的客户还依赖我们的服务,我不能让他们失望!所以着火也不管了,女朋友喊我也无所谓,我开始
|
著名的领导力准则"客户至上",有很多的客户还依赖我们的服务,我不能让他们失望!所以着火也不管了,女朋友喊我也无所谓,我开始
|
||||||
debug 这个线上问题。
|
debug 这个线上问题。
|
||||||
</NTabPane>
|
</NTabPane>
|
||||||
</NTabs>
|
</NTabs>
|
||||||
|
@ -7,6 +7,7 @@ import { fetchCreateLeave, fetchGetLeaveDetail, fetchStartWorkflow, fetchUpdateL
|
|||||||
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';
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'LeaveEdit'
|
name: 'LeaveEdit'
|
||||||
@ -36,6 +37,8 @@ const emit = defineEmits<Emits>();
|
|||||||
const visible = defineModel<boolean>('visible', {
|
const visible = defineModel<boolean>('visible', {
|
||||||
default: false
|
default: false
|
||||||
});
|
});
|
||||||
|
const approvalInfoPanelRef = ref<InstanceType<typeof ApprovalInfoPanel>>();
|
||||||
|
|
||||||
const { bool: taskApplyVisible, setTrue: setTaskApplyVisible } = useBoolean();
|
const { bool: taskApplyVisible, setTrue: setTaskApplyVisible } = useBoolean();
|
||||||
const { formRef, validate, restoreValidation } = useNaiveForm();
|
const { formRef, validate, restoreValidation } = useNaiveForm();
|
||||||
const { createRequiredRule } = useFormRules();
|
const { createRequiredRule } = useFormRules();
|
||||||
@ -91,7 +94,9 @@ function createDefaultModelDetail(): ModelDetail {
|
|||||||
remark: ''
|
remark: ''
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
const showApprovalInfoPanel = computed(() => {
|
||||||
|
return modelDetail.status !== 'draft';
|
||||||
|
});
|
||||||
type StartWorkflowModel = Api.Workflow.StartWorkflowOperateParams;
|
type StartWorkflowModel = Api.Workflow.StartWorkflowOperateParams;
|
||||||
|
|
||||||
const startWorkflowModel: StartWorkflowModel = reactive(createDefaultStartWorkflowModel());
|
const startWorkflowModel: StartWorkflowModel = reactive(createDefaultStartWorkflowModel());
|
||||||
@ -207,16 +212,20 @@ function handleTaskFinished() {
|
|||||||
emit('submitted');
|
emit('submitted');
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(visible, () => {
|
watch(visible, async () => {
|
||||||
if (visible.value) {
|
if (visible.value) {
|
||||||
handleUpdateModelWhenEdit();
|
await handleUpdateModelWhenEdit();
|
||||||
restoreValidation();
|
restoreValidation();
|
||||||
|
|
||||||
|
if (showApprovalInfoPanel.value) {
|
||||||
|
approvalInfoPanelRef.value?.initData();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NDrawer v-model:show="visible" :title="title" display-directive="show" :width="1000" 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">
|
<div v-if="!readonly">
|
||||||
<NForm ref="formRef" :model="model" :rules="rules">
|
<NForm ref="formRef" :model="model" :rules="rules">
|
||||||
@ -259,8 +268,8 @@ watch(visible, () => {
|
|||||||
{{ model.remark || '-' }}
|
{{ model.remark || '-' }}
|
||||||
</NDescriptionsItem>
|
</NDescriptionsItem>
|
||||||
</NDescriptions>
|
</NDescriptions>
|
||||||
<!-- -->
|
<!-- 审批信息 -->
|
||||||
<ApprovalInfoPanel v-if="modelDetail.status !== 'draft'" :business-id="modelDetail.id!" />
|
<ApprovalInfoPanel v-if="showApprovalInfoPanel" ref="approvalInfoPanelRef" :business-id="modelDetail.id!" />
|
||||||
</div>
|
</div>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<div v-if="!readonly">
|
<div v-if="!readonly">
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { reactive, ref, watch } from 'vue';
|
import { reactive, ref, watch } from 'vue';
|
||||||
|
import type { UploadFileInfo } from 'naive-ui';
|
||||||
import { messageTypeOptions } from '@/constants/workflow';
|
import { messageTypeOptions } from '@/constants/workflow';
|
||||||
import { fetchCompleteTask, fetchGetTask } from '@/service/api/workflow';
|
import { fetchCompleteTask, fetchGetTask } from '@/service/api/workflow';
|
||||||
import FileUpload from '@/components/custom/file-upload.vue';
|
import FileUpload from '@/components/custom/file-upload.vue';
|
||||||
@ -30,8 +31,6 @@ const title = defineModel<string>('title', {
|
|||||||
default: '流程发起'
|
default: '流程发起'
|
||||||
});
|
});
|
||||||
|
|
||||||
const fileUploadRef = ref<InstanceType<typeof FileUpload> | null>(null);
|
|
||||||
|
|
||||||
const accept = ref<string>('.doc,.docx,.xls,.xlsx,.ppt,.pptx,.txt,.pdf,.jpg,.jpeg,.png,.gif,.bmp,.webp');
|
const accept = ref<string>('.doc,.docx,.xls,.xlsx,.ppt,.pptx,.txt,.pdf,.jpg,.jpeg,.png,.gif,.bmp,.webp');
|
||||||
|
|
||||||
type Model = Api.Workflow.CompleteTaskOperateParams;
|
type Model = Api.Workflow.CompleteTaskOperateParams;
|
||||||
@ -58,10 +57,11 @@ async function getTask() {
|
|||||||
task.value = data;
|
task.value = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fileList = ref<UploadFileInfo[]>([]);
|
||||||
|
|
||||||
async function handleSubmit() {
|
async function handleSubmit() {
|
||||||
const fileList = fileUploadRef.value?.fileList;
|
if (fileList.value?.length) {
|
||||||
if (fileList?.length) {
|
const fileIds = fileList.value.map(item => item.id);
|
||||||
const fileIds = fileList.map(item => item.id);
|
|
||||||
model.fileId = fileIds.join(',');
|
model.fileId = fileIds.join(',');
|
||||||
}
|
}
|
||||||
model.taskId = props.taskId;
|
model.taskId = props.taskId;
|
||||||
@ -82,7 +82,7 @@ watch(visible, () => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NModal v-model:show="visible" preset="card" class="w-800px" :title="title" :native-scrollbar="false" closable>
|
<NModal v-model:show="visible" preset="card" class="w-700px" :title="title" :native-scrollbar="false" closable>
|
||||||
<NForm :model="model">
|
<NForm :model="model">
|
||||||
<NFormItem label="通知方式" path="messageType">
|
<NFormItem label="通知方式" path="messageType">
|
||||||
<NCheckboxGroup v-model:value="model.messageType">
|
<NCheckboxGroup v-model:value="model.messageType">
|
||||||
@ -98,7 +98,7 @@ watch(visible, () => {
|
|||||||
</NCheckboxGroup>
|
</NCheckboxGroup>
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
<NFormItem label="附件" path="fileId">
|
<NFormItem label="附件" path="fileId">
|
||||||
<FileUpload ref="fileUploadRef" :file-size="20" :max="20" upload-type="file" :accept="accept" />
|
<FileUpload v-model:file-list="fileList" :file-size="20" :max="20" upload-type="file" :accept="accept" />
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
</NForm>
|
</NForm>
|
||||||
<div class="flex justify-end gap-12px">
|
<div class="flex justify-end gap-12px">
|
||||||
|
2
src/typings/api/workflow.api.d.ts
vendored
2
src/typings/api/workflow.api.d.ts
vendored
@ -374,6 +374,8 @@ declare namespace Api {
|
|||||||
version: string;
|
version: string;
|
||||||
/** 运行时长 */
|
/** 运行时长 */
|
||||||
runDuration: string;
|
runDuration: string;
|
||||||
|
/** 附件 */
|
||||||
|
attachmentList: Api.System.Oss[];
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
type InstanceIdWithHisTask = CommonType.RecordNullable<{
|
type InstanceIdWithHisTask = CommonType.RecordNullable<{
|
||||||
|
1
src/typings/components.d.ts
vendored
1
src/typings/components.d.ts
vendored
@ -97,6 +97,7 @@ declare module 'vue' {
|
|||||||
NGi: typeof import('naive-ui')['NGi']
|
NGi: typeof import('naive-ui')['NGi']
|
||||||
NGrid: typeof import('naive-ui')['NGrid']
|
NGrid: typeof import('naive-ui')['NGrid']
|
||||||
NGridItem: typeof import('naive-ui')['NGridItem']
|
NGridItem: typeof import('naive-ui')['NGridItem']
|
||||||
|
NIcon: typeof import('naive-ui')['NIcon']
|
||||||
NInput: typeof import('naive-ui')['NInput']
|
NInput: typeof import('naive-ui')['NInput']
|
||||||
NInputGroup: typeof import('naive-ui')['NInputGroup']
|
NInputGroup: typeof import('naive-ui')['NInputGroup']
|
||||||
NInputGroupLabel: typeof import('naive-ui')['NInputGroupLabel']
|
NInputGroupLabel: typeof import('naive-ui')['NInputGroupLabel']
|
||||||
|
Loading…
Reference in New Issue
Block a user