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; }