feat(components): 增强审批信息面板,优化附件处理
This commit is contained in:
parent
496ed978ca
commit
49224afe2d
@ -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>
|
||||
|
@ -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">
|
||||
|
@ -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">
|
||||
|
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;
|
||||
/** 运行时长 */
|
||||
runDuration: string;
|
||||
/** 附件 */
|
||||
attachmentList: Api.System.Oss[];
|
||||
}>;
|
||||
|
||||
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']
|
||||
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']
|
||||
|
Loading…
Reference in New Issue
Block a user