feat: 合同管理上传附件模块

This commit is contained in:
louis 2024-03-01 10:47:50 +08:00
parent 434b634375
commit 3ff2e732b2
6 changed files with 322 additions and 9 deletions

2
.env
View File

@ -1,5 +1,5 @@
# 项目名称 # 项目名称
VITE_APP_TITLE = Admin VITE_APP_TITLE = 华信OA
# 网站前缀 # 网站前缀
VITE_BASE_URL = / VITE_BASE_URL = /

View File

@ -0,0 +1,11 @@
<template>
<div>test</div>
</template>
<script setup lang="ts">
defineOptions({
name: 'FileManage',
});
</script>
<style lang="less" scoped></style>

View File

@ -10,17 +10,17 @@ export const baseColumns = (ctx: { contractTypes: API.DictItemEntity[] }): Table
const { contractTypes } = ctx; const { contractTypes } = ctx;
return [ return [
{ {
title: '合同编号', title: '编号',
width: 120, width: 120,
dataIndex: 'contractNumber', dataIndex: 'contractNumber',
}, },
{ {
title: '合同标题', title: '标题',
width: 200, width: 200,
dataIndex: 'title', dataIndex: 'title',
}, },
{ {
title: '合同类型', title: '类型',
width: 80, width: 80,
formItemProps: { formItemProps: {
component: 'Select', component: 'Select',

View File

@ -20,29 +20,34 @@
</template> </template>
</DynamicTable> </DynamicTable>
</div> </div>
<!-- <UploadModal v-if="isUploadPopupVisiable" :visiable="isUploadPopupVisiable"></UploadModal> -->
</template> </template>
<script setup lang="ts"> <script setup lang="tsx">
import { useTable } from '@/components/core/dynamic-table'; import { useTable } from '@/components/core/dynamic-table';
import { baseColumns, type TableColumnItem, type TableListItem } from './columns'; import { baseColumns, type TableColumnItem, type TableListItem } from './columns';
import Api from '@/api/'; import Api from '@/api/';
import { useDictStore } from '@/store/modules/dict'; import { useDictStore } from '@/store/modules/dict';
import { onMounted, ref } from 'vue'; import { onMounted, ref } from 'vue';
import { DictEnum } from '@/enums/dictEnum'; import { DictEnum } from '@/enums/dictEnum';
import { useFormModal } from '@/hooks/useModal'; import { useFormModal, useModal } from '@/hooks/useModal';
import { contractSchemas } from './formSchemas'; import { contractSchemas } from './formSchemas';
import { formatToDate } from '@/utils/dateUtil'; import { formatToDate } from '@/utils/dateUtil';
import UploadContract from './upload-contract.vue';
defineOptions({ defineOptions({
name: 'Contract', name: 'Contract',
}); });
const [DynamicTable, dynamicTableInstance] = useTable(); const [DynamicTable, dynamicTableInstance] = useTable();
const [showModal] = useFormModal(); const [showModal] = useFormModal();
const [fnModal] = useModal();
const { getDictItemsByCode } = useDictStore(); const { getDictItemsByCode } = useDictStore();
const contractTypes = ref<API.DictItemEntity[]>([]); const contractTypes = ref<API.DictItemEntity[]>([]);
const getContractTypes = async () => { const getContractTypes = async () => {
contractTypes.value = await getDictItemsByCode(DictEnum.ContractType); contractTypes.value = await getDictItemsByCode(DictEnum.ContractType);
}; };
const isUploadPopupVisiable = ref(false);
// contractList; // contractList;
let columns = ref<TableColumnItem[]>(); let columns = ref<TableColumnItem[]>();
onMounted(() => { onMounted(() => {
@ -55,7 +60,7 @@ import { formatToDate } from '@/utils/dateUtil';
title: '操作', title: '操作',
maxWidth: 100, maxWidth: 100,
width: 100, width: 100,
minWidth:100, minWidth: 100,
dataIndex: 'ACTION', dataIndex: 'ACTION',
hideInSearch: true, hideInSearch: true,
fixed: 'right', fixed: 'right',
@ -80,12 +85,37 @@ import { formatToDate } from '@/utils/dateUtil';
onConfirm: () => delRowConfirm(record.id), onConfirm: () => delRowConfirm(record.id),
}, },
}, },
{
icon: 'ant-design:cloud-server-outlined',
tooltip: '上传下载附件',
onClick: () => openFileManageModal(record),
},
], ],
}, },
]; ];
}); });
}); });
const openFileManageModal = async (record: API.ContractEntity) => {
// UseModalComp.show({
// title: '',
// content: FileManage,
// });
isUploadPopupVisiable.value = true;
fnModal.show({
width: 800,
title: `合同编号: ${record.contractNumber}`,
content: () => {
return <UploadContract onClose={handleUploadClose}></UploadContract>;
},
footer: null,
});
};
const handleUploadClose = (hasSuccess: boolean) => {
fnModal.hide();
//
// fnModal.hide();
};
/** /**
* @description 打开新增/编辑弹窗 * @description 打开新增/编辑弹窗
*/ */

View File

@ -0,0 +1,153 @@
import { Tag, Tooltip, Image, Progress } from 'ant-design-vue';
import type { TableColumn } from '@/components/core/dynamic-table';
import type { FormSchema } from '@/components/core/schema-form';
import { formatToDateTime } from '@/utils/dateUtil';
import { baseApiUrl } from '@/utils/request';
export type TableListItem = API.StorageInfo;
export type TableColumnItem = TableColumn<TableListItem>;
export type FileItem = {
file: File;
uid: string;
name: string;
size: number;
status: string;
thumbUrl: string;
percent: number;
};
export enum UploadResultStatus {
SUCCESS = 'success',
ERROR = 'error',
UPLOADING = 'uploading',
}
export const baseColumns: TableColumnItem[] = [
{
title: '文件名',
dataIndex: 'name',
width: 150,
ellipsis: true,
customRender({ record }) {
return (
<Tooltip>
{{
title: () => record.path,
default: () => (
<a href={baseApiUrl + record.path} target="_blank">
{record.name}
</a>
),
}}
</Tooltip>
);
},
},
{
title: '预览图',
dataIndex: 'path',
width: 150,
customRender({ record }) {
return <Image src={baseApiUrl + record.path}></Image>;
},
},
{
title: '文件后缀',
dataIndex: 'extName',
width: 80,
},
{
title: '类别',
dataIndex: 'type',
width: 80,
},
{
title: '大小',
dataIndex: 'size',
width: 80,
customRender: ({ record }) => {
return <Tag color="blue">{record.size}</Tag>;
},
},
{
title: '上传者',
dataIndex: 'username',
width: 120,
},
{
title: '创建时间',
dataIndex: 'createdAt',
width: 160,
customRender: ({ record }) => formatToDateTime(record.createdAt),
},
];
export const fileListColumns: TableColumn<FileItem>[] = [
{
dataIndex: 'thumbUrl',
title: '缩略图',
width: 100,
customRender: ({ record }) => {
const { thumbUrl } = record;
return thumbUrl && <Image src={thumbUrl} />;
},
},
{
dataIndex: 'name',
title: '文件名',
align: 'left',
customRender: ({ text, record }) => {
const { percent, status: uploadStatus } = record || {};
let status: 'normal' | 'exception' | 'active' | 'success' = 'normal';
if (uploadStatus === UploadResultStatus.ERROR) {
status = 'exception';
} else if (uploadStatus === UploadResultStatus.UPLOADING) {
status = 'active';
} else if (uploadStatus === UploadResultStatus.SUCCESS) {
status = 'success';
}
return (
<div>
<p class="truncate mb-1 max-w-[280px]" title={text}>
{text}
</p>
<Progress percent={percent} size="small" status={status} />
</div>
);
},
},
{
dataIndex: 'size',
title: '文件大小',
width: 100,
customRender: ({ text = 0 }) => {
return text && `${(text / 1024).toFixed(2)}KB`;
},
},
{
dataIndex: 'status',
title: '状态',
width: 100,
customRender: ({ text }) => {
if (text === UploadResultStatus.SUCCESS) {
return <Tag color="green"></Tag>;
} else if (text === UploadResultStatus.ERROR) {
return <Tag color="red"></Tag>;
} else if (text === UploadResultStatus.UPLOADING) {
return <Tag color="blue"></Tag>;
}
return text || '待上传';
},
},
];
export const searchFormSchema: FormSchema[] = [
{
field: 'name',
label: '名称',
component: 'Input',
colProps: { span: 8 },
},
];

View File

@ -0,0 +1,119 @@
<template>
<a-flex justify="space-between" align="center">
<a-alert message="单个文件不超过5MB最多只能上传10个文件" type="info" show-icon />
<a-upload :multiple="true" :before-upload="beforeUpload" :show-upload-list="false">
<a-button type="primary"> 选择文件 </a-button>
</a-upload>
</a-flex>
<DynamicTable :search="false" :data-source="fileList" :columns="columns" />
<a-flex justify="flex-end" gap="10">
<Button @click="onCancel">取消</Button>
<Button :type="'primary'" @click="onOk" :disabled="disabledUpload">上传</Button></a-flex
>
</template>
<script setup lang="tsx">
import { ref, computed } from 'vue';
import { message, type UploadProps } from 'ant-design-vue';
import { UploadResultStatus, fileListColumns, type FileItem } from './upload-columns';
import { useTable, type TableColumn } from '@/components/core/dynamic-table';
import Button from '@/components/basic/button';
import Api from '@/api/';
const emit = defineEmits(['close']);
const [DynamicTable] = useTable();
const fileList = ref<FileItem[]>([]);
const disabledUpload = computed(() => {
return !fileList.value.some((n) => n.status !== UploadResultStatus.SUCCESS);
});
const fileToBase64 = (file: File): Promise<string> => {
const { promise, resolve, reject } = Promise.withResolvers<string>();
const reader = new FileReader();
reader.onload = () => {
resolve(reader.result as string);
};
reader.onerror = (error) => {
reject(error);
};
reader.readAsDataURL(file);
return promise;
};
const onCancel = () => {
const hasSuccess = fileList.value.some((n) => n.status === UploadResultStatus.SUCCESS);
emit('close', hasSuccess);
fileList.value = [];
};
const onOk = async () => {
const uploadFileList = fileList.value.filter((n) => n.status !== UploadResultStatus.SUCCESS);
await Promise.all(
uploadFileList.map(async (item) => {
try {
await Api.toolsUpload.uploadUpload({ file: item.file }, undefined, {
onUploadProgress(progressEvent) {
const complete = ((progressEvent.loaded / progressEvent.total!) * 100) | 0;
item.percent = complete;
item.status = UploadResultStatus.UPLOADING;
},
});
item.status = UploadResultStatus.SUCCESS;
} catch (error) {
console.log(error);
item.status = UploadResultStatus.ERROR;
}
}),
);
};
const beforeUpload: UploadProps['beforeUpload'] = async (file) => {
if (file.size / 1024 / 1024 > 5) {
message.error('单个文件不超过5MB');
} else {
const item: FileItem = {
file,
uid: file.uid,
name: file.name,
size: file.size,
status: '',
percent: 0,
thumbUrl: await fileToBase64(file),
};
fileList.value.push(item);
}
return false;
};
const handleRemove = (record: FileItem) => {
fileList.value = fileList.value.filter((n) => n.uid !== record.uid);
};
const columns: TableColumn<FileItem>[] = [
...fileListColumns,
{
width: 120,
title: '操作',
dataIndex: 'ACTION',
fixed: false,
actions: ({ record }) => [
{
label: '删除',
color: 'red',
onClick: () => handleRemove(record),
},
],
},
];
</script>
<style scoped></style>