import type { Ref } from 'vue'; import type { AxiosProgressEvent, InfraFileApi } from '#/api/infra/file'; import { computed, unref } from 'vue'; import { useAppConfig } from '@vben/hooks'; import { $t } from '@vben/locales'; import { createFile, getFilePresignedUrl, uploadFile } from '#/api/infra/file'; import { baseRequestClient } from '#/api/request'; const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD); /** * 上传类型 */ enum UPLOAD_TYPE { // 客户端直接上传(只支持S3服务) CLIENT = 'client', // 客户端发送到后端上传 SERVER = 'server', } /** * 上传类型钩子函数 * @param acceptRef 接受的文件类型 * @param helpTextRef 帮助文本 * @param maxNumberRef 最大文件数量 * @param maxSizeRef 最大文件大小 * @returns 文件类型限制和帮助文本的计算属性 */ export function useUploadType({ acceptRef, helpTextRef, maxNumberRef, maxSizeRef, }: { acceptRef: Ref; helpTextRef: Ref; maxNumberRef: Ref; maxSizeRef: Ref; }) { // 文件类型限制 const getAccept = computed(() => { const accept = unref(acceptRef); if (accept && accept.length > 0) { return accept; } return []; }); const getStringAccept = computed(() => { return unref(getAccept) .map((item) => { return item.indexOf('/') > 0 || item.startsWith('.') ? item : `.${item}`; }) .join(','); }); // 支持jpg、jpeg、png格式,不超过2M,最多可选择10张图片,。 const getHelpText = computed(() => { const helpText = unref(helpTextRef); if (helpText) { return helpText; } const helpTexts: string[] = []; const accept = unref(acceptRef); if (accept.length > 0) { helpTexts.push($t('ui.upload.accept', [accept.join(',')])); } const maxSize = unref(maxSizeRef); if (maxSize) { helpTexts.push($t('ui.upload.maxSize', [maxSize])); } const maxNumber = unref(maxNumberRef); if (maxNumber && maxNumber !== Infinity) { helpTexts.push($t('ui.upload.maxNumber', [maxNumber])); } return helpTexts.join(','); }); return { getAccept, getStringAccept, getHelpText }; } /** * 上传钩子函数 * @param directory 上传目录 * @returns 上传 URL 和自定义上传方法 */ export function useUpload(directory?: string) { // 后端上传地址 const uploadUrl = getUploadUrl(); // 是否使用前端直连上传 const isClientUpload = UPLOAD_TYPE.CLIENT === import.meta.env.VITE_UPLOAD_TYPE; // 重写ElUpload上传方法 async function httpRequest( file: File, onUploadProgress?: AxiosProgressEvent, ) { // 模式一:前端上传 if (isClientUpload) { // 1.1 生成文件名称 const fileName = await generateFileName(file); // 1.2 获取文件预签名地址 const presignedInfo = await getFilePresignedUrl(fileName, directory); // 1.3 上传文件 return baseRequestClient .put(presignedInfo.uploadUrl, file, { headers: { 'Content-Type': file.type, }, }) .then(() => { // 1.4. 记录文件信息到后端(异步) createFile0(presignedInfo, file); // 通知成功,数据格式保持与后端上传的返回结果一致 return { url: presignedInfo.url }; }); } else { // 模式二:后端上传 return uploadFile({ file, directory }, onUploadProgress); } } return { uploadUrl, httpRequest, }; } /** * 获得上传 URL */ export function getUploadUrl(): string { return `${apiURL}/infra/file/upload`; } /** * 创建文件信息 * * @param vo 文件预签名信息 * @param file 文件 */ function createFile0( vo: InfraFileApi.FilePresignedUrlRespVO, file: File, ): InfraFileApi.File { const fileVO = { configId: vo.configId, url: vo.url, path: vo.path, name: file.name, type: file.type, size: file.size, }; createFile(fileVO); return fileVO; } /** * 生成文件名称(使用算法SHA256) * * @param file 要上传的文件 */ async function generateFileName(file: File) { // // 读取文件内容 // const data = await file.arrayBuffer(); // const wordArray = CryptoJS.lib.WordArray.create(data); // // 计算SHA256 // const sha256 = CryptoJS.SHA256(wordArray).toString(); // // 拼接后缀 // const ext = file.name.slice(Math.max(0, file.name.lastIndexOf('.'))); // return `${sha256}${ext}`; return file.name; }