diff --git a/inventory-backend/app/api/v1/common/upload.py b/inventory-backend/app/api/v1/common/upload.py index f219e51..8403139 100644 --- a/inventory-backend/app/api/v1/common/upload.py +++ b/inventory-backend/app/api/v1/common/upload.py @@ -25,10 +25,10 @@ BASE_DIR = get_project_root() UPLOAD_FOLDER = os.path.join(BASE_DIR, 'uploads') # 允许上传的文件后缀 -ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif', 'bmp', 'webp', 'pdf', 'doc', 'docx', 'xls', 'xlsx'} +ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif', 'bmp', 'webp', 'pdf', 'doc', 'docx', 'xls', 'xlsx', 'zip', 'rar', '7z'} -# ★ 文件上传安全加固:限制最大文件大小 (10MB) -MAX_CONTENT_LENGTH = 10 * 1024 * 1024 # 10MB +# ★ 文件上传安全加固:限制最大文件大小 (50MB,支持压缩包) +MAX_CONTENT_LENGTH = 50 * 1024 * 1024 # 50MB def allowed_file(filename): @@ -68,7 +68,7 @@ def upload_file(): if file_size > MAX_CONTENT_LENGTH: return jsonify({ "code": 400, - "msg": f"文件大小超过限制 ({MAX_CONTENT_LENGTH // (1024*1024)}MB)" + "msg": f"文件大小超过限制(最大 50MB)" }), 400 if file and allowed_file(file.filename): diff --git a/inventory-web/src/views/material/list.vue b/inventory-web/src/views/material/list.vue index 2f4bb5f..cdd7d0d 100644 --- a/inventory-web/src/views/material/list.vue +++ b/inventory-web/src/views/material/list.vue @@ -273,7 +273,8 @@
- + + {{ link.split('/').pop() }}
@@ -515,7 +516,7 @@ - + @@ -1534,6 +1535,7 @@ const tableRowClassName = ({ row }: { row: MaterialBaseVO }) => { const getImageUrl = (url: string) => { return !url ? '' : (url.startsWith('http') ? url : url) } const isExternalLink = (str: string) => { return str && (str.startsWith('http://') || str.startsWith('https://')) && !str.includes('/api/v1/common/files') } const isImageFile = (url: string) => { return /\.(jpg|jpeg|png|gif|webp|bmp)$/i.test(url) } +const isCompressedFile = (url: string) => { return /\.(zip|rar|7z)$/i.test(url) } const getImagesOnly = (list: string[]) => { return !list ? [] : list.filter(item => !isExternalLink(item) && isImageFile(item)) } const getNonImagesOnly = (list: string[]) => { return !list ? [] : list.filter(item => !isExternalLink(item) && !isImageFile(item)) } const truncateFileName = (name: string, maxLen = 12) => { return name.length > maxLen ? name.slice(0, maxLen - 3) + '...' : name } @@ -1550,13 +1552,23 @@ const handleDownloadConfirm = (link: string) => { } const beforeAvatarUpload = (rawFile: any) => { - const isTypeValid = ['image/jpeg', 'image/png', 'application/pdf'].includes(rawFile.type); + const isTypeValid = [ + 'image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/bmp', + 'application/pdf', + 'application/zip', 'application/x-zip-compressed', + 'application/x-rar-compressed', + 'application/vnd.rar', + 'application/x-7z-compressed', + 'application/octet-stream' // 兼容某些浏览器对 .zip/.rar 的错误识别 + ].includes(rawFile.type); if (!isTypeValid) { - ElMessage.error('仅支持 JPG/PNG/PDF'); + ElMessage.error('仅支持 JPG/PNG/GIF/PDF/ZIP/RAR/7Z'); return false; } - if (rawFile.size / 1024 / 1024 > 10) { - ElMessage.error('文件不能超过 10MB'); + const isCompressed = ['application/zip', 'application/x-zip-compressed', 'application/x-rar-compressed', 'application/vnd.rar', 'application/x-7z-compressed'].includes(rawFile.type); + const maxMB = isCompressed ? 50 : 10; + if (rawFile.size / 1024 / 1024 > maxMB) { + ElMessage.error(`文件不能超过 ${maxMB}MB`); return false; } return true; @@ -1586,6 +1598,21 @@ const customUpload = async (options: any, targetField: 'generalImage' | 'general } const handleRemoveImage = async (uploadFile: any, targetField: 'generalImage' | 'generalManual') => { + const fileName = uploadFile.name || uploadFile.url?.split('/').pop() || '此文件' + try { + await ElMessageBox.confirm( + `确认要删除「${fileName}」吗?删除后不可恢复。`, + '删除确认', + { + confirmButtonText: '确认删除', + cancelButtonText: '取消', + type: 'warning' + } + ) + } catch { + return // 用户取消,不删除 + } + try { const urlToRemove = form.value[targetField].find(u => getImageUrl(u) === uploadFile.url) || uploadFile.url form.value[targetField] = form.value[targetField].filter(u => u !== urlToRemove) @@ -1593,8 +1620,11 @@ const handleRemoveImage = async (uploadFile: any, targetField: 'generalImage' | const filename = urlToRemove.split('/').pop(); if (filename) await deleteFile(filename) } - ElMessage.success('已从列表移除') - } catch (e) { console.error(e) } + ElMessage.success('已删除') + } catch (e) { + console.error(e) + ElMessage.error('删除失败') + } } const handlePreviewPicture = (uploadFile: any) => {