feat: 合同管理上传附件模块
This commit is contained in:
parent
434b634375
commit
3ff2e732b2
2
.env
2
.env
|
@ -1,5 +1,5 @@
|
|||
# 项目名称
|
||||
VITE_APP_TITLE = Admin
|
||||
VITE_APP_TITLE = 华信OA
|
||||
|
||||
# 网站前缀
|
||||
VITE_BASE_URL = /
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
<template>
|
||||
<div>test</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineOptions({
|
||||
name: 'FileManage',
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
|
@ -10,17 +10,17 @@ export const baseColumns = (ctx: { contractTypes: API.DictItemEntity[] }): Table
|
|||
const { contractTypes } = ctx;
|
||||
return [
|
||||
{
|
||||
title: '合同编号',
|
||||
title: '编号',
|
||||
width: 120,
|
||||
dataIndex: 'contractNumber',
|
||||
},
|
||||
{
|
||||
title: '合同标题',
|
||||
title: '标题',
|
||||
width: 200,
|
||||
dataIndex: 'title',
|
||||
},
|
||||
{
|
||||
title: '合同类型',
|
||||
title: '类型',
|
||||
width: 80,
|
||||
formItemProps: {
|
||||
component: 'Select',
|
||||
|
|
|
@ -20,29 +20,34 @@
|
|||
</template>
|
||||
</DynamicTable>
|
||||
</div>
|
||||
<!-- <UploadModal v-if="isUploadPopupVisiable" :visiable="isUploadPopupVisiable"></UploadModal> -->
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
<script setup lang="tsx">
|
||||
import { useTable } from '@/components/core/dynamic-table';
|
||||
import { baseColumns, type TableColumnItem, type TableListItem } from './columns';
|
||||
import Api from '@/api/';
|
||||
import { useDictStore } from '@/store/modules/dict';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { DictEnum } from '@/enums/dictEnum';
|
||||
import { useFormModal } from '@/hooks/useModal';
|
||||
import { useFormModal, useModal } from '@/hooks/useModal';
|
||||
import { contractSchemas } from './formSchemas';
|
||||
import { formatToDate } from '@/utils/dateUtil';
|
||||
import { formatToDate } from '@/utils/dateUtil';
|
||||
import UploadContract from './upload-contract.vue';
|
||||
defineOptions({
|
||||
name: 'Contract',
|
||||
});
|
||||
const [DynamicTable, dynamicTableInstance] = useTable();
|
||||
const [showModal] = useFormModal();
|
||||
const [fnModal] = useModal();
|
||||
const { getDictItemsByCode } = useDictStore();
|
||||
const contractTypes = ref<API.DictItemEntity[]>([]);
|
||||
|
||||
const getContractTypes = async () => {
|
||||
contractTypes.value = await getDictItemsByCode(DictEnum.ContractType);
|
||||
};
|
||||
const isUploadPopupVisiable = ref(false);
|
||||
|
||||
// contractList;
|
||||
let columns = ref<TableColumnItem[]>();
|
||||
onMounted(() => {
|
||||
|
@ -55,7 +60,7 @@ import { formatToDate } from '@/utils/dateUtil';
|
|||
title: '操作',
|
||||
maxWidth: 100,
|
||||
width: 100,
|
||||
minWidth:100,
|
||||
minWidth: 100,
|
||||
dataIndex: 'ACTION',
|
||||
hideInSearch: true,
|
||||
fixed: 'right',
|
||||
|
@ -80,12 +85,37 @@ import { formatToDate } from '@/utils/dateUtil';
|
|||
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 打开新增/编辑弹窗
|
||||
*/
|
||||
|
|
|
@ -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 },
|
||||
},
|
||||
];
|
|
@ -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>
|
Loading…
Reference in New Issue