feat(components): 增强审批信息面板,优化附件处理

This commit is contained in:
AN 2025-06-19 23:14:25 +08:00
parent 496ed978ca
commit 49224afe2d
5 changed files with 146 additions and 29 deletions

View File

@ -1,20 +1,31 @@
<script setup lang="tsx">
import { onMounted, ref } from 'vue';
import { ref } from 'vue';
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 { fetchGetOssListByIds } from '@/service/api/system/oss';
import { useDict } from '@/hooks/business/dict';
import { useDownload } from '@/hooks/business/download';
import DictTag from '@/components/custom/dict-tag.vue';
defineOptions({
name: 'ApprovalInfoPanel'
});
interface Props {
/** 业务id */
businessId: CommonType.IdType;
}
useDict('wf_task_status');
const props = defineProps<Props>();
useDict('wf_task_status');
const activeTab = ref('info');
const { loading, startLoading, endLoading } = useLoading();
const { oss } = useDownload();
const columns = ref<DataTableColumns<Api.Workflow.HisTask>>([
{
title: '任务名称',
@ -28,10 +39,36 @@ const columns = ref<DataTableColumns<Api.Workflow.HisTask>>([
align: 'center',
width: 100,
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 (
<NTag size="small" type="info">
{row.approveName}
</NTag>
<NPopover trigger="hover" placement="bottom">
{{
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: '开始时间',
key: 'createTime',
align: 'center',
width: 100
width: 120
},
{
title: '结束时间',
key: 'updateTime',
align: 'center',
width: 100
width: 120
},
{
title: '运行时间',
@ -72,25 +109,86 @@ const columns = ref<DataTableColumns<Api.Workflow.HisTask>>([
title: '附件',
key: 'attachmentList',
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 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() {
const { error, data } = await fetchGetFlowHisTaskList(props.businessId);
if (error) {
window.$message?.error(error.message);
return;
}
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(() => {
getData();
defineExpose({
initData
});
</script>
@ -99,14 +197,21 @@ onMounted(() => {
<div>
<NTabs v-model:value="activeTab" type="segment" animated>
<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 bar-width="100px" name="image" tab="流程图">
威尔着火了快来帮忙我听到女朋友大喊现在一个难题在我面前是恢复一个重要的 Amazon 服务还是救公寓的火
"威尔!着火了!快来帮忙!"我听到女朋友大喊现在一个难题在我面前是恢复一个重要的 Amazon 服务还是救公寓的火
<br />
<br />
我的脑海中忽然出现了 Amazon
著名的领导力准则客户至上有很多的客户还依赖我们的服务我不能让他们失望所以着火也不管了女朋友喊我也无所谓我开始
著名的领导力准则"客户至上"有很多的客户还依赖我们的服务我不能让他们失望所以着火也不管了女朋友喊我也无所谓我开始
debug 这个线上问题
</NTabPane>
</NTabs>

View File

@ -7,6 +7,7 @@ import { fetchCreateLeave, fetchGetLeaveDetail, fetchStartWorkflow, fetchUpdateL
import { useFormRules, useNaiveForm } from '@/hooks/common/form';
import { useDict } from '@/hooks/business/dict';
import { $t } from '@/locales';
import ApprovalInfoPanel from '@/components/custom/work-flow/approval-info-panel.vue';
defineOptions({
name: 'LeaveEdit'
@ -36,6 +37,8 @@ const emit = defineEmits<Emits>();
const visible = defineModel<boolean>('visible', {
default: false
});
const approvalInfoPanelRef = ref<InstanceType<typeof ApprovalInfoPanel>>();
const { bool: taskApplyVisible, setTrue: setTaskApplyVisible } = useBoolean();
const { formRef, validate, restoreValidation } = useNaiveForm();
const { createRequiredRule } = useFormRules();
@ -91,7 +94,9 @@ function createDefaultModelDetail(): ModelDetail {
remark: ''
};
}
const showApprovalInfoPanel = computed(() => {
return modelDetail.status !== 'draft';
});
type StartWorkflowModel = Api.Workflow.StartWorkflowOperateParams;
const startWorkflowModel: StartWorkflowModel = reactive(createDefaultStartWorkflowModel());
@ -207,16 +212,20 @@ function handleTaskFinished() {
emit('submitted');
}
watch(visible, () => {
watch(visible, async () => {
if (visible.value) {
handleUpdateModelWhenEdit();
await handleUpdateModelWhenEdit();
restoreValidation();
if (showApprovalInfoPanel.value) {
approvalInfoPanelRef.value?.initData();
}
}
});
</script>
<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>
<div v-if="!readonly">
<NForm ref="formRef" :model="model" :rules="rules">
@ -259,8 +268,8 @@ watch(visible, () => {
{{ model.remark || '-' }}
</NDescriptionsItem>
</NDescriptions>
<!-- -->
<ApprovalInfoPanel v-if="modelDetail.status !== 'draft'" :business-id="modelDetail.id!" />
<!-- 审批信息 -->
<ApprovalInfoPanel v-if="showApprovalInfoPanel" ref="approvalInfoPanelRef" :business-id="modelDetail.id!" />
</div>
<template #footer>
<div v-if="!readonly">

View File

@ -1,5 +1,6 @@
<script setup lang="ts">
import { reactive, ref, watch } from 'vue';
import type { UploadFileInfo } from 'naive-ui';
import { messageTypeOptions } from '@/constants/workflow';
import { fetchCompleteTask, fetchGetTask } from '@/service/api/workflow';
import FileUpload from '@/components/custom/file-upload.vue';
@ -30,8 +31,6 @@ const title = defineModel<string>('title', {
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');
type Model = Api.Workflow.CompleteTaskOperateParams;
@ -58,10 +57,11 @@ async function getTask() {
task.value = data;
}
const fileList = ref<UploadFileInfo[]>([]);
async function handleSubmit() {
const fileList = fileUploadRef.value?.fileList;
if (fileList?.length) {
const fileIds = fileList.map(item => item.id);
if (fileList.value?.length) {
const fileIds = fileList.value.map(item => item.id);
model.fileId = fileIds.join(',');
}
model.taskId = props.taskId;
@ -82,7 +82,7 @@ watch(visible, () => {
</script>
<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">
<NFormItem label="通知方式" path="messageType">
<NCheckboxGroup v-model:value="model.messageType">
@ -98,7 +98,7 @@ watch(visible, () => {
</NCheckboxGroup>
</NFormItem>
<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>
</NForm>
<div class="flex justify-end gap-12px">

View File

@ -374,6 +374,8 @@ declare namespace Api {
version: string;
/** 运行时长 */
runDuration: string;
/** 附件 */
attachmentList: Api.System.Oss[];
}>;
type InstanceIdWithHisTask = CommonType.RecordNullable<{

View File

@ -97,6 +97,7 @@ declare module 'vue' {
NGi: typeof import('naive-ui')['NGi']
NGrid: typeof import('naive-ui')['NGrid']
NGridItem: typeof import('naive-ui')['NGridItem']
NIcon: typeof import('naive-ui')['NIcon']
NInput: typeof import('naive-ui')['NInput']
NInputGroup: typeof import('naive-ui')['NInputGroup']
NInputGroupLabel: typeof import('naive-ui')['NInputGroupLabel']