|
|
|
@ -58,19 +58,24 @@
|
|
|
|
<text class="section-title">附件</text>
|
|
|
|
<text class="section-title">附件</text>
|
|
|
|
</view>
|
|
|
|
</view>
|
|
|
|
<view class="attachment-area">
|
|
|
|
<view class="attachment-area">
|
|
|
|
<view class="attachment-list">
|
|
|
|
<view v-if="attachmentFileName" class="attachment-file-item">
|
|
|
|
<view v-for="(file, idx) in attachmentList" :key="idx" class="attachment-file-item">
|
|
|
|
<view class="file-icon">
|
|
|
|
<view class="file-icon"><text class="file-icon-text">{{ getFileIcon(file.name) }}</text></view>
|
|
|
|
<text class="file-icon-text">{{ getFileIcon(attachmentFileName) }}</text>
|
|
|
|
<view class="file-info">
|
|
|
|
</view>
|
|
|
|
<text class="file-name">{{ file.name }}</text>
|
|
|
|
<view class="file-info">
|
|
|
|
<text class="file-size">{{ formatFileSize(file.size) }}</text>
|
|
|
|
<text class="file-name" :title="attachmentFileName">{{ attachmentFileName }}</text>
|
|
|
|
</view>
|
|
|
|
<text v-if="attachmentFileSize" class="file-size">{{ formatFileSize(attachmentFileSize) }}</text>
|
|
|
|
<view class="file-delete" @click="removeAttachment(idx)"><text class="delete-icon-sm">✕</text></view>
|
|
|
|
</view>
|
|
|
|
|
|
|
|
<view v-if="uploadLoading" class="file-uploading">
|
|
|
|
|
|
|
|
<text class="uploading-text">上传中...</text>
|
|
|
|
</view>
|
|
|
|
</view>
|
|
|
|
<view class="attachment-add-file" @click="handleAddAttachment">
|
|
|
|
<view v-else class="file-delete" @click="removeAttachment">
|
|
|
|
<text class="add-text">选取文件</text>
|
|
|
|
<text class="delete-icon-sm">✕</text>
|
|
|
|
</view>
|
|
|
|
</view>
|
|
|
|
</view>
|
|
|
|
</view>
|
|
|
|
|
|
|
|
<view class="attachment-add-file" @click="handleAddAttachment" v-if="!attachmentFileName">
|
|
|
|
|
|
|
|
<text class="add-text">选取文件</text>
|
|
|
|
|
|
|
|
</view>
|
|
|
|
</view>
|
|
|
|
</view>
|
|
|
|
</view>
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
@ -121,6 +126,8 @@ import { useI18n } from 'vue-i18n'
|
|
|
|
import NavBar from '@/components/common/NavBar.vue'
|
|
|
|
import NavBar from '@/components/common/NavBar.vue'
|
|
|
|
import { createSparepartOutbound } from '@/api/mes/sparepartOutbound'
|
|
|
|
import { createSparepartOutbound } from '@/api/mes/sparepartOutbound'
|
|
|
|
import { getSparepartDetail } from '@/api/mes/sparepart'
|
|
|
|
import { getSparepartDetail } from '@/api/mes/sparepart'
|
|
|
|
|
|
|
|
import { getBaseUrl } from '@/utils/request'
|
|
|
|
|
|
|
|
import { getToken } from '@/utils/auth'
|
|
|
|
|
|
|
|
|
|
|
|
const { t } = useI18n()
|
|
|
|
const { t } = useI18n()
|
|
|
|
|
|
|
|
|
|
|
|
@ -129,7 +136,11 @@ const outboundDate = ref(formatDate(new Date()))
|
|
|
|
const selectedOperatorId = ref(null)
|
|
|
|
const selectedOperatorId = ref(null)
|
|
|
|
const selectedOperatorName = ref('')
|
|
|
|
const selectedOperatorName = ref('')
|
|
|
|
const remark = 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 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 }
|
|
|
|
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 getFileIcon(fileName) {
|
|
|
|
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 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() {
|
|
|
|
function handleAddAttachment() {
|
|
|
|
// #ifdef APP-PLUS
|
|
|
|
// #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
|
|
|
|
// #endif
|
|
|
|
// #ifndef APP-PLUS
|
|
|
|
// #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
|
|
|
|
// #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() {
|
|
|
|
async function handleSubmit() {
|
|
|
|
if (!itemList.value.length) { uni.showToast({ title: '请先添加备件', icon: 'none' }); return }
|
|
|
|
if (!itemList.value.length) { uni.showToast({ title: '请先添加备件', icon: 'none' }); return }
|
|
|
|
@ -250,7 +345,7 @@ async function handleSubmit() {
|
|
|
|
items: items
|
|
|
|
items: items
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (attachmentList.value.length) { submitData.fileUrl = String(attachmentList.value[0]?.path || '') }
|
|
|
|
if (fileUrl.value) { submitData.fileUrl = fileUrl.value }
|
|
|
|
|
|
|
|
|
|
|
|
console.log('=== 出库提交 ===')
|
|
|
|
console.log('=== 出库提交 ===')
|
|
|
|
console.log(JSON.stringify(submitData))
|
|
|
|
console.log(JSON.stringify(submitData))
|
|
|
|
@ -312,6 +407,7 @@ onHide(() => {})
|
|
|
|
.file-size { font-size: 22rpx; color: #9ca3af; margin-top: 4rpx; display: block; }
|
|
|
|
.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; }
|
|
|
|
.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; }
|
|
|
|
.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; }
|
|
|
|
.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; }
|
|
|
|
.add-text { font-size: 26rpx; color: #6b7280; }
|
|
|
|
|
|
|
|
|
|
|
|
|