From 2eeffb1fda9aa0ba9155c9cbccbf500e405a30f1 Mon Sep 17 00:00:00 2001 From: zhongwenkai <3478244299@qq.com> Date: Tue, 23 Jun 2026 10:17:21 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A4=87=E4=BB=B6=E5=87=BA=E5=85=A5?= =?UTF-8?q?=E5=BA=93=E9=99=84=E4=BB=B6=E9=80=89=E6=8B=A9=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pages/sparepartInbound/create.vue | 178 ++++++++++++++---- .../pages/sparepartOutbound/create.vue | 130 +++++++++++-- 2 files changed, 251 insertions(+), 57 deletions(-) diff --git a/src/pages_function/pages/sparepartInbound/create.vue b/src/pages_function/pages/sparepartInbound/create.vue index 2b5f1c1..f28fda3 100644 --- a/src/pages_function/pages/sparepartInbound/create.vue +++ b/src/pages_function/pages/sparepartInbound/create.vue @@ -67,22 +67,23 @@ 附件 - - - - {{ getFileIcon(file.name) }} - - - {{ file.name }} - {{ formatFileSize(file.size) }} - - - - + + + {{ getFileIcon(attachmentFileName) }} + + + {{ attachmentFileName }} + {{ formatFileSize(attachmentFileSize) }} - - 选取文件 + + 上传中... + + + + + + 选取文件 @@ -143,6 +144,8 @@ import { useI18n } from 'vue-i18n' import NavBar from '@/components/common/NavBar.vue' import { createSparepartInbound } from '@/api/mes/sparepartInbound' import { getSparepartDetail } from '@/api/mes/sparepart' +import { getBaseUrl } from '@/utils/request' +import { getToken } from '@/utils/auth' const { t } = useI18n() @@ -208,8 +211,11 @@ const selectedOperatorName = ref('') // 备注 const remark = ref('') -// 附件 -const attachmentList = ref([]) +// 附件(单文件,web端 limit=1) +const fileUrl = ref('') // JSON: {"fileName":"xxx.pdf","fileUrl":"https://..."} +const attachmentFileName = ref('') // 展示用:文件名 +const attachmentFileSize = ref(0) // 展示用:文件大小 +const uploadLoading = ref(false) // 上传中状态 function formatDate(date) { const y = date.getFullYear() @@ -288,42 +294,130 @@ function formatFileSize(bytes) { return (bytes / (1024 * 1024)).toFixed(1) + ' MB' } +const ALLOWED_EXTS = ['png', 'jpg', 'jpeg', 'doc', 'xls', 'ppt', 'txt', 'pdf'] +const MAX_FILE_SIZE = 100 * 1024 * 1024 // 100MB + function handleAddAttachment() { // #ifdef APP-PLUS + // APP端用 plus.gallery 选择文件 plus.gallery.pick( (res) => { - const files = (res?.files || []).map(f => ({ - name: f.name || 'unknown', - size: f.size || 0, - path: f // 保留原始 file 对象 - })) - attachmentList.value.push(...files) - }, - (e) => { - console.log('选择文件失败:', e) + const file = res?.files?.[0] + if (!file) return + const ext = (file.name || '').split('.').pop()?.toLowerCase() + if (!ALLOWED_EXTS.includes(ext)) { + uni.showToast({ title: '不支持的文件类型:.' + (ext || '未知'), icon: 'none' }) + return + } + if (file.size > MAX_FILE_SIZE) { + uni.showToast({ title: '文件大小不能超过100MB', icon: 'none' }) + return + } + uploadFile(file) }, - { filter: 'all', multiple: true, maximum: 9 - attachmentList.value.length } + (e) => { console.log('选择文件失败:', e) }, + { filter: 'all', multiple: false } ) // #endif // #ifndef APP-PLUS - // 非APP端暂用 chooseImage 兜底 - uni.chooseImage({ - count: 9 - attachmentList.value.length, - sizeType: ['compressed'], - sourceType: ['album', 'camera'], + // 非APP端用 uni.chooseFile 选择文件 + uni.chooseFile({ + count: 1, + type: 'all', success: (res) => { - res.tempFilePaths.forEach(path => { - const parts = path.split('/') - const name = parts[parts.length - 1] || 'image.jpg' - attachmentList.value.push({ name, size: 0, path }) + const tempFile = res.tempFiles?.[0] + if (!tempFile) return + const ext = (tempFile.name || '').split('.').pop()?.toLowerCase() + if (!ALLOWED_EXTS.includes(ext)) { + uni.showToast({ title: '不支持的文件类型:.' + (ext || '未知'), icon: 'none' }) + return + } + if (tempFile.size > MAX_FILE_SIZE) { + uni.showToast({ title: '文件大小不能超过100MB', icon: 'none' }) + return + } + uploadFile(tempFile) + }, + fail: () => { + // 兜底:不支持 chooseFile 的环境用 chooseImage + uni.chooseImage({ + count: 1, + sizeType: ['compressed'], + sourceType: ['album', 'camera'], + success: (imgRes) => { + const path = imgRes.tempFilePaths?.[0] + if (!path) return + const parts = path.split('/') + const name = parts[parts.length - 1] || 'image.jpg' + uploadFile({ path, name, size: 0 }) + } }) } }) // #endif } -function removeAttachment(idx) { - attachmentList.value.splice(idx, 1) +async function uploadFile(file) { + uploadLoading.value = true + attachmentFileName.value = file.name || 'unknown' + attachmentFileSize.value = file.size || 0 + + // 构建 form-data + const formData = { + file: file.path // uni.uploadFile 的 files 参数需要的是路径 + } + + try { + const res = await new Promise((resolve, reject) => { + uni.uploadFile({ + url: getBaseUrl() + '/admin-api/infra/file/upload', + filePath: file.path, + name: 'file', + header: { + 'Authorization': 'Bearer ' + (getToken() || ''), + 'tenantId': '1' + }, + success: (uploadRes) => { + if (uploadRes.statusCode === 200) { + try { + const data = JSON.parse(uploadRes.data) + resolve(data) + } catch (e) { + reject(new Error('解析上传结果失败')) + } + } else { + reject(new Error('上传失败,状态码:' + uploadRes.statusCode)) + } + }, + fail: (err) => { + reject(err) + } + }) + }) + + if (res.code === 0 && res.data) { + const { fileName, fileUrl: url } = res.data + fileUrl.value = JSON.stringify({ fileName: fileName || file.name, fileUrl: url }) + attachmentFileName.value = fileName || file.name + uni.showToast({ title: '上传成功', icon: 'success' }) + } else { + throw new Error(res.msg || '上传失败') + } + } catch (e) { + attachmentFileName.value = '' + attachmentFileSize.value = 0 + fileUrl.value = '' + const msg = e?.message || '上传失败' + uni.showToast({ title: String(msg).substring(0, 50), icon: 'none' }) + } finally { + uploadLoading.value = false + } +} + +function removeAttachment() { + attachmentFileName.value = '' + attachmentFileSize.value = 0 + fileUrl.value = '' } async function handleSubmit() { @@ -373,9 +467,9 @@ async function handleSubmit() { items: items } - // 附件(文件对象数组) - if (attachmentList.value.length) { - submitData.attachments = attachmentList.value.map(f => f.path || f) + // 附件(单文件,与 web 端一致存 JSON 字符串) + if (fileUrl.value) { + submitData.fileUrl = fileUrl.value } console.log('提交数据:', JSON.stringify(submitData)) @@ -561,6 +655,10 @@ onHide(() => {}) flex-shrink: 0; } .delete-icon-sm { font-size: 22rpx; color: #dc2626; } +.file-uploading { + flex-shrink: 0; + .uploading-text { font-size: 22rpx; color: #2563eb; } +} .attachment-add-file { display: flex; align-items: center; justify-content: center; gap: 10rpx; diff --git a/src/pages_function/pages/sparepartOutbound/create.vue b/src/pages_function/pages/sparepartOutbound/create.vue index 7339308..b4f8934 100644 --- a/src/pages_function/pages/sparepartOutbound/create.vue +++ b/src/pages_function/pages/sparepartOutbound/create.vue @@ -58,19 +58,24 @@ 附件 - - - {{ getFileIcon(file.name) }} - - {{ file.name }} - {{ formatFileSize(file.size) }} - - + + + {{ getFileIcon(attachmentFileName) }} + + + {{ attachmentFileName }} + {{ formatFileSize(attachmentFileSize) }} + + + 上传中... - - 选取文件 + + + + 选取文件 + @@ -121,6 +126,8 @@ import { useI18n } from 'vue-i18n' import NavBar from '@/components/common/NavBar.vue' import { createSparepartOutbound } from '@/api/mes/sparepartOutbound' import { getSparepartDetail } from '@/api/mes/sparepart' +import { getBaseUrl } from '@/utils/request' +import { getToken } from '@/utils/auth' const { t } = useI18n() @@ -129,7 +136,11 @@ const outboundDate = ref(formatDate(new Date())) const selectedOperatorId = ref(null) const selectedOperatorName = ref('') const remark = ref('') -const attachmentList = ref([]) +// 附件(单文件) +const fileUrl = ref('') +const attachmentFileName = ref('') +const attachmentFileSize = ref(0) +const uploadLoading = ref(false) function formatDate(date) { const y = date.getFullYear(); const m = String(date.getMonth() + 1).padStart(2, '0'); const d = String(date.getDate()).padStart(2, '0'); return `${y}-${m}-${d}` } function handleDateChange(e) { outboundDate.value = e.detail.value } @@ -202,17 +213,101 @@ function goSelectOperator() { } // 附件 -function getFileIcon(fileName) { const ext = (fileName || '').split('.').pop()?.toLowerCase(); const m = { pdf: '📄', doc: '📝', docx: '📝', xls: '📊', xlsx: '📊', ppt: '📽', pptx: '📽', txt: '📃', zip: '📦', rar: '📦', jpg: '🖼', jpeg: '🖼', png: '🖼', gif: '🖼' }; return m[ext] || '📎' } -function formatFileSize(bytes) { if (!bytes) return '0 B'; if (bytes < 1024) return bytes + ' B'; if (bytes < 1048576) return (bytes / 1024).toFixed(1) + ' KB'; return (bytes / 1048576).toFixed(1) + ' MB' } +function getFileIcon(fileName) { + const ext = (fileName || '').split('.').pop()?.toLowerCase() + const iconMap = { pdf: '📄', doc: '📝', docx: '📝', xls: '📊', xlsx: '📊', ppt: '📽', pptx: '📽', txt: '📃', zip: '📦', rar: '📦', jpg: '🖼', jpeg: '🖼', png: '🖼', gif: '🖼', webp: '🖼' } + return iconMap[ext] || '📎' +} +function formatFileSize(bytes) { + if (!bytes) return '0 B' + if (bytes < 1024) return bytes + ' B' + if (bytes < 1048576) return (bytes / 1024).toFixed(1) + ' KB' + return (bytes / 1048576).toFixed(1) + ' MB' +} + +const ALLOWED_EXTS = ['png', 'jpg', 'jpeg', 'doc', 'xls', 'ppt', 'txt', 'pdf'] +const MAX_FILE_SIZE = 100 * 1024 * 1024 + function handleAddAttachment() { // #ifdef APP-PLUS - plus.gallery.pick((res) => { const files = (res?.files || []).map(f => ({ name: f.name || 'unknown', size: f.size || 0, path: f })); attachmentList.value.push(...files) }, (e) => {}, { filter: 'all', multiple: true, maximum: 9 - attachmentList.value.length }) + plus.gallery.pick( + (res) => { + const file = res?.files?.[0] + if (!file) return + const ext = (file.name || '').split('.').pop()?.toLowerCase() + if (!ALLOWED_EXTS.includes(ext)) { uni.showToast({ title: '不支持的文件类型:.' + (ext || '未知'), icon: 'none' }); return } + if (file.size > MAX_FILE_SIZE) { uni.showToast({ title: '文件大小不能超过100MB', icon: 'none' }); return } + uploadFile(file) + }, + (e) => { console.log('选择文件失败:', e) }, + { filter: 'all', multiple: false } + ) // #endif // #ifndef APP-PLUS - uni.chooseImage({ count: 9 - attachmentList.value.length, sizeType: ['compressed'], sourceType: ['album', 'camera'], success: (res) => { res.tempFilePaths.forEach(path => { const parts = path.split('/'); const name = parts[parts.length - 1] || 'image.jpg'; attachmentList.value.push({ name, size: 0, path }) }) } }) + uni.chooseFile({ + count: 1, type: 'all', + success: (res) => { + const tempFile = res.tempFiles?.[0] + if (!tempFile) return + const ext = (tempFile.name || '').split('.').pop()?.toLowerCase() + if (!ALLOWED_EXTS.includes(ext)) { uni.showToast({ title: '不支持的文件类型:.' + (ext || '未知'), icon: 'none' }); return } + if (tempFile.size > MAX_FILE_SIZE) { uni.showToast({ title: '文件大小不能超过100MB', icon: 'none' }); return } + uploadFile(tempFile) + }, + fail: () => { + uni.chooseImage({ + count: 1, sizeType: ['compressed'], sourceType: ['album', 'camera'], + success: (imgRes) => { + const path = imgRes.tempFilePaths?.[0] + if (!path) return + const parts = path.split('/') + uploadFile({ path, name: parts[parts.length - 1] || 'image.jpg', size: 0 }) + } + }) + } + }) // #endif } -function removeAttachment(idx) { attachmentList.value.splice(idx, 1) } + +async function uploadFile(file) { + uploadLoading.value = true + attachmentFileName.value = file.name || 'unknown' + attachmentFileSize.value = file.size || 0 + try { + const res = await new Promise((resolve, reject) => { + uni.uploadFile({ + url: getBaseUrl() + '/admin-api/infra/file/upload', + filePath: file.path, + name: 'file', + header: { 'Authorization': 'Bearer ' + (getToken() || ''), 'tenantId': '1' }, + success: (uploadRes) => { + if (uploadRes.statusCode === 200) { + try { resolve(JSON.parse(uploadRes.data)) } + catch (e) { reject(new Error('解析上传结果失败')) } + } else { reject(new Error('上传失败,状态码:' + uploadRes.statusCode)) } + }, + fail: (err) => { reject(err) } + }) + }) + if (res.code === 0 && res.data) { + const { fileName, fileUrl: url } = res.data + fileUrl.value = JSON.stringify({ fileName: fileName || file.name, fileUrl: url }) + attachmentFileName.value = fileName || file.name + uni.showToast({ title: '上传成功', icon: 'success' }) + } else { + throw new Error(res.msg || '上传失败') + } + } catch (e) { + attachmentFileName.value = ''; attachmentFileSize.value = 0; fileUrl.value = '' + uni.showToast({ title: String(e?.message || '上传失败').substring(0, 50), icon: 'none' }) + } finally { + uploadLoading.value = false + } +} + +function removeAttachment() { + attachmentFileName.value = ''; attachmentFileSize.value = 0; fileUrl.value = '' +} async function handleSubmit() { if (!itemList.value.length) { uni.showToast({ title: '请先添加备件', icon: 'none' }); return } @@ -250,7 +345,7 @@ async function handleSubmit() { items: items } - if (attachmentList.value.length) { submitData.fileUrl = String(attachmentList.value[0]?.path || '') } + if (fileUrl.value) { submitData.fileUrl = fileUrl.value } console.log('=== 出库提交 ===') console.log(JSON.stringify(submitData)) @@ -312,6 +407,7 @@ onHide(() => {}) .file-size { font-size: 22rpx; color: #9ca3af; margin-top: 4rpx; display: block; } .file-delete { width: 48rpx; height: 48rpx; border-radius: 24rpx; background: #fee2e2; display: flex; align-items: center; justify-content: center; flex-shrink: 0; } .delete-icon-sm { font-size: 22rpx; color: #dc2626; } +.file-uploading { flex-shrink: 0; .uploading-text { font-size: 22rpx; color: #2563eb; } } .attachment-add-file { display: flex; align-items: center; justify-content: center; height: 88rpx; background: #fff; border: 2rpx dashed #cbd5e1; border-radius: 12rpx; } .add-text { font-size: 26rpx; color: #6b7280; }