20260518172616

This commit is contained in:
oneao committed 2026-05-18 17:26:17 +08:00
1 parent 2e704bbbb0
commit 5cfc87eee8
8 files changed
+500 -276

No files matched your search

@@ -2,7 +2,7 @@
import type { UploadFile, UploadFileItem } from 'sard-uniapp'
import { dialog, isImageUrl, isVideoUrl, toast } from 'sard-uniapp'
import { getOrgBaseUrl } from '@/request'
import { deleteFileApi, loadDataApi, saveObjectApi, uploadContainerFileApi } from '@/request/api'
import { deleteFileApi, deleteFilesApi, loadDataApi, saveObjectApi, uploadContainerFileApi } from '@/request/api'
import { useUserStore } from '@/store'
import { buildPubicUrl, downloadFile, getFileExtension, imageCompress, isImage } from '@/utils/file'
@@ -14,6 +14,12 @@ const form = ref<any>({
b_container_img: '',
})
const subFileListRef = ref()
const subFileListRef2 = ref()
const oldSubFileList = ref([])
const oldSubFileList2 = ref([])
const subFileList = ref([]) // 装货照片
const subFileList2 = ref([]) // 发货单、签收单、过磅单
@@ -101,6 +107,9 @@ async function loadMainData() {
subFileList2.value = allFiles
.filter(f => f.mx_cate_id === '2')
.map(formatFile)
oldSubFileList.value = JSON.parse(JSON.stringify(subFileList.value))
oldSubFileList2.value = JSON.parse(JSON.stringify(subFileList2.value))
}
}
catch (err) {
@@ -111,51 +120,19 @@ async function loadMainData() {
}
}
function onItemClick(fileItem: UploadFileItem) {
console.log(fileItem)
if (!isImageUrl(fileItem.mx_mapfilename) && !isVideoUrl(fileItem.mx_mapfilename)) {
downloadFile(fileItem.mx_mapfilename, fileItem.name)
}
}
function beforeRead(file: UploadFile): boolean | Promise<UploadFile> {
// 非图片直接允许上传
if (!isImage(file.name))
return true
// 图片异步压缩,返回修改后的 UploadFile 对象
return imageCompress(file.path).then((res) => {
file.path = res.path
file.size = res.size
return file
}).catch((err) => {
console.error('图片压缩失败:', err)
return file
function handleBeforeRemove({ file, fileList, resolve }) {
dialog.confirm({
title: '提示',
message: '确定删除该文件吗?',
onConfirm: () => {
resolve(true)
},
onCancel: () => {
resolve(false)
},
})
}
function beforeRemove(_index: number, _fileItem: UploadFileItem): boolean | Promise<void> {
return new Promise((resolve, reject) => {
dialog.confirm({
title: '提示',
message: `确定删除该张图片吗?`,
onConfirm: () => resolve(),
onCancel: () => reject(),
onClose: () => reject(),
})
})
}
function onRemove(index: number, item: UploadFileItem) {
if (item.subid) {
deleteFileApi(item.subid).then((res) => {
if (res.success) {
toast.success('删除成功')
}
})
}
}
async function handleConfirm() {
if (form.value.b_container_img === '') {
toast.fail('集装箱号图片不能为空')
@@ -177,91 +154,209 @@ async function handleConfirm() {
transparent: false,
})
const fileInserts = []
const fileUpdates = []
try {
const fileInserts = []
const fileUpdates = []
const fileDeletes = []
const allFiles = [
...subFileList.value.map(item => ({ ...item, cate: '1' })),
...subFileList2.value.map(item => ({ ...item, cate: '2' })),
]
// =========================
// 当前上传组件文件
// =========================
const subFiles
= subFileListRef.value?.$.setupState.uploadFiles || []
const grouped = allFiles.reduce((acc, item) => {
if (!acc[item.cate])
acc[item.cate] = []
acc[item.cate].push(item)
return acc
}, {})
const subFiles2
= subFileListRef2.value?.$.setupState.uploadFiles || []
for (const cate in grouped) {
let index = 1
// 当前所有文件
const allFiles = [
...subFiles.map(item => ({
...item,
cate: '1',
})),
...subFiles2.map(item => ({
...item,
cate: '2',
})),
]
for (const fileItem of grouped[cate]) {
const mx_xh = index++
// 原始文件
const oldFiles = [
...(oldSubFileList.value || []).map(item => ({
...item,
cate: '1',
})),
...(oldSubFileList2.value || []).map(item => ({
...item,
cate: '2',
})),
]
// 已存在 → update
if (fileItem.subid) {
fileUpdates.push({
subid: fileItem.subid,
mx_xh,
mx_cate_id: cate,
})
continue
// =========================
// 删除文件
// =========================
const currentSubIds = new Set(
allFiles
.map(item => item.subid)
.filter(Boolean),
)
fileDeletes.push(
...oldFiles
.filter(item =>
item.subid
&& !currentSubIds.has(item.subid),
)
.map(item => ({
subid: item.subid,
})),
)
// =========================
// 分类排序
// =========================
const grouped = allFiles.reduce((acc, item) => {
if (!acc[item.cate]) {
acc[item.cate] = []
}
// 新增 → upload
const res = await uploadContainerFileApi(fileItem.file.path, {
jobId: form.value.b_id,
containerNo: form.value.b_container_no,
fileName: fileItem.name,
})
acc[item.cate].push(item)
if (!res.success)
continue
return acc
}, {})
fileInserts.push({
subid: res.data.uid,
father: form.value.b_id,
mx_xh,
mx_filename: res.data.fileName,
mx_filesize: fileItem.file.size,
mx_fileext: getFileExtension(fileItem.file.name),
mx_mapfilename: res.data.path,
mx_moduleid: 'b_container',
mx_cate_id: cate,
})
// =========================
// 处理新增/更新
// =========================
for (const cate in grouped) {
let index = 1
fileItem.subid = res.data.uid
for (const fileItem of grouped[cate]) {
const mx_xh = index++
// 已存在 → update
if (fileItem.subid) {
fileUpdates.push({
subid: fileItem.subid,
mx_xh,
mx_cate_id: cate,
})
continue
}
try {
// 上传路径
let uploadPath
= fileItem.path
|| fileItem.url
|| fileItem.tempFilePath
// 图片压缩
if (isImage(fileItem.name)) {
const compressRes = await imageCompress(uploadPath)
uploadPath = compressRes.path
}
// 上传文件
const res = await uploadContainerFileApi(
uploadPath,
{
jobId: form.value.b_id,
containerNo: form.value.b_container_no,
fileName: fileItem.name,
},
)
if (!res.success) {
continue
}
// 新增记录
fileInserts.push({
subid: res.data.uid,
father: form.value.b_id,
mx_xh,
mx_filename: res.data.fileName,
mx_filesize: fileItem.size,
mx_fileext: getFileExtension(fileItem.name),
mx_mapfilename: res.data.path,
mx_moduleid: 'b_container',
mx_cate_id: cate,
})
// 回填 subid
fileItem.subid = res.data.uid
}
catch (err) {
console.error('附件上传失败:', fileItem, err)
}
}
}
}
if (fileInserts.length === 0 && fileUpdates.length === 0) {
toast.success('保存成功')
// =========================
// 先删除物理文件
// =========================
if (fileDeletes.length > 0) {
const deleteRes = await deleteFilesApi(
fileDeletes.map(item => item.subid),
)
setTimeout(() => {
loadMainData()
}, 1000)
return
}
if (!deleteRes.success) {
toast.fail('删除文件失败')
const reqData = [
{
tableName: 'bf_file',
keyField: 'subid',
inserts: fileInserts,
updates: fileUpdates,
deletes: [],
},
]
return
}
}
saveObjectApi(reqData).then((res) => {
if (res.success) {
// =========================
// 无新增/更新
// 删除已经单独处理了
// =========================
if (
fileInserts.length === 0
&& fileUpdates.length === 0
) {
toast.success('保存成功')
setTimeout(() => {
loadMainData()
}, 1000)
return
}
})
// =========================
// 保存数据
// =========================
const reqData = [
{
tableName: 'bf_file',
keyField: 'subid',
inserts: fileInserts,
updates: fileUpdates,
deletes: [],
},
]
const res = await saveObjectApi(reqData)
if (res.success) {
toast.success('保存成功')
}
else {
toast.fail('保存失败')
}
}
catch (err) {
console.error(err)
toast.fail('保存失败')
}
finally {
setTimeout(() => {
toast.hide()
loadMainData()
}, 1000)
}
}
// 集装箱号校验
@@ -385,13 +480,14 @@ const templateCollapse = ref<string[]>(['template1'])
</view>
</template>
<view class="step-content">
<sar-upload
v-model="subFileList"
<wd-upload
ref="subFileListRef"
v-model:file-list="subFileList"
:auto-upload="false"
:before-remove="handleBeforeRemove"
multiple
:before-read="beforeRead"
:before-remove="beforeRemove"
@item-click="onItemClick"
@remove="onRemove"
accept="all"
:max-size="50 * 1024 * 1024"
/>
</view>
</wd-cell-group>
@@ -406,13 +502,14 @@ const templateCollapse = ref<string[]>(['template1'])
</view>
</template>
<view class="step-content">
<sar-upload
v-model="subFileList2"
<wd-upload
ref="subFileListRef2"
v-model:file-list="subFileList2"
:auto-upload="false"
:before-remove="handleBeforeRemove"
multiple
:before-read="beforeRead"
:before-remove="beforeRemove"
@item-click="onItemClick"
@remove="onRemove"
accept="all"
:max-size="50 * 1024 * 1024"
/>
</view>
</wd-cell-group>
@@ -19,6 +19,9 @@ const form = ref({
ocrNo: '',
})
const subFileListRef = ref()
const subFileListRef2 = ref()
const subFileList = ref([]) // 装货照片
const subFileList2 = ref([]) // 发货单、签收单、过磅单
@@ -114,38 +117,16 @@ function handleDeleteBoxImage() {
resetForm()
}
function onItemClick(fileItem: UploadFileItem) {
console.log(fileItem)
if (!isImageUrl(fileItem.mx_mapfilename) && !isVideoUrl(fileItem.mx_mapfilename)) {
downloadFile(fileItem.mx_mapfilename, fileItem.name)
}
}
function beforeRead(file: UploadFile): boolean | Promise<UploadFile> {
// 非图片直接允许上传
if (!isImage(file.name))
return true
// 图片异步压缩,返回修改后的 UploadFile 对象
return imageCompress(file.path).then((res) => {
file.path = res.path
file.size = res.size
return file
}).catch((err) => {
console.error('图片压缩失败:', err)
return file
})
}
function beforeRemove(_index: number, _fileItem: UploadFileItem): boolean | Promise<void> {
return new Promise((resolve, reject) => {
dialog.confirm({
title: '提示',
message: `确定删除该张图片吗?`,
onConfirm: () => resolve(),
onCancel: () => reject(),
onClose: () => reject(),
})
function handleBeforeRemove({ file, fileList, resolve }) {
dialog.confirm({
title: '提示',
message: '确定删除该文件吗?',
onConfirm: () => {
resolve(true)
},
onCancel: () => {
resolve(false)
},
})
}
@@ -155,7 +136,7 @@ async function handleConfirm() {
return
}
if (form.value.b_container_no === '') {
if (!form.value.b_container_no) {
toast.fail('集装箱号不能为空')
return
}
@@ -170,112 +151,154 @@ async function handleConfirm() {
transparent: false,
})
const mainTable = {
b_id: form.value.b_id,
b_container_no: form.value.b_container_no,
b_container_img: form.value.b_container_img,
b_ocr_no: form.value.ocrNo,
b_ocr_score: form.value.ocrScore || 0.00,
}
const fileInserts = []
try {
const mainTable = {
b_id: form.value.b_id,
b_container_no: form.value.b_container_no,
b_container_img: form.value.b_container_img,
b_ocr_no: form.value.ocrNo,
b_ocr_score: form.value.ocrScore || 0.00,
}
// 主表
await uploadContainerFileApi(boxImage.value.path, {
jobId: form.value.b_id,
containerNo: form.value.b_container_no,
fileName: boxImage.value.name,
}).then((res) => {
if (res.success) {
mainTable.b_container_img = res.data.path
const fileInserts = []
// =========================
// 上传主图
// =========================
const mainRes = await uploadContainerFileApi(
boxImage.value.path,
{
jobId: form.value.b_id,
containerNo: form.value.b_container_no,
fileName: boxImage.value.name,
},
)
if (mainRes.success) {
mainTable.b_container_img = mainRes.data.path
fileInserts.push({
subid: res.data.uid,
subid: mainRes.data.uid,
father: form.value.b_id,
mx_xh: 0,
mx_filename: res.data.fileName,
mx_filename: mainRes.data.fileName,
mx_filesize: boxImage.value.size,
mx_fileext: getFileExtension(boxImage.value.name),
mx_mapfilename: res.data.path,
mx_mapfilename: mainRes.data.path,
mx_moduleid: 'b_container',
mx_cate_id: '0',
})
}
})
const indexMap = {}
// =========================
// 附件排序
// =========================
const indexMap = {}
const getNextIndex = (cate) => {
if (!indexMap[cate]) {
indexMap[cate] = 1
const getNextIndex = (cate) => {
if (!indexMap[cate]) {
indexMap[cate] = 1
}
return indexMap[cate]++
}
return indexMap[cate]++
}
const allFiles = [
...subFileList.value.map(item => ({
...item,
cate: '1',
})),
...subFileList2.value.map(item => ({
...item,
cate: '2',
})),
]
// =========================
// 获取附件
// =========================
const subFiles = subFileListRef.value?.$.setupState.uploadFiles || []
const subFiles2 = subFileListRef2.value?.$.setupState.uploadFiles || []
for (const fileItem of allFiles) {
const res = await uploadContainerFileApi(fileItem.file.path, {
jobId: form.value.b_id,
containerNo: form.value.b_container_no,
fileName: fileItem.name,
})
const allFiles = [
...subFiles.map(item => ({
...item,
cate: '1',
})),
...subFiles2.map(item => ({
...item,
cate: '2',
})),
]
if (!res.success)
continue
// =========================
// 上传附件
// =========================
for (const fileItem of allFiles) {
try {
let uploadPath = fileItem.url
fileInserts.push({
subid: res.data.uid,
father: form.value.b_id,
mx_xh: getNextIndex(fileItem.cate),
mx_filename: res.data.fileName,
mx_filesize: fileItem.file.size,
mx_fileext: getFileExtension(fileItem.file.name),
mx_mapfilename: res.data.path,
mx_moduleid: 'b_container',
mx_cate_id: fileItem.cate,
})
}
// 图片压缩
if (isImage(fileItem.name)) {
const compressRes = await imageCompress(fileItem.url)
uploadPath = compressRes.path
}
const reqData = [
{
tableName: 'b_container',
keyField: 'b_id',
inserts: [mainTable],
updates: [],
deletes: [],
},
{
tableName: 'bf_file',
keyField: 'subid',
inserts: fileInserts,
updates: [],
deletes: [],
},
]
const res = await uploadContainerFileApi(uploadPath, {
jobId: form.value.b_id,
containerNo: form.value.b_container_no,
fileName: fileItem.name,
})
saveObjectApi(reqData).then((res) => {
if (res.success) {
if (!res.success) {
continue
}
fileInserts.push({
subid: res.data.uid,
father: form.value.b_id,
mx_xh: getNextIndex(fileItem.cate),
mx_filename: res.data.fileName,
mx_filesize: fileItem.size,
mx_fileext: getFileExtension(fileItem.name),
mx_mapfilename: res.data.path,
mx_moduleid: 'b_container',
mx_cate_id: fileItem.cate,
})
}
catch (err) {
console.error('附件上传失败:', fileItem, err)
}
}
// =========================
// 保存数据
// =========================
const reqData = [
{
tableName: 'b_container',
keyField: 'b_id',
inserts: [mainTable],
updates: [],
deletes: [],
},
{
tableName: 'bf_file',
keyField: 'subid',
inserts: fileInserts,
updates: [],
deletes: [],
},
]
const saveRes = await saveObjectApi(reqData)
if (saveRes.success) {
toast.success('保存成功')
tipContainerNo.value = form.value.b_container_no
showResult.value = true
}
else {
toast.fail('保存失败')
}
}).catch(() => {
}
catch (err) {
console.error(err)
toast.fail('保存失败')
}).finally(() => {
}
finally {
toast.hide()
})
}
}
// 集装箱号校验
@@ -446,12 +469,14 @@ const templateCollapse = ref<string[]>(['template1'])
</view>
</template>
<view class="step-content">
<sar-upload
v-model="subFileList"
<wd-upload
ref="subFileListRef"
v-model:file-list="subFileList"
:auto-upload="false"
:before-remove="handleBeforeRemove"
multiple
:before-read="beforeRead"
:before-remove="beforeRemove"
@item-click="onItemClick"
accept="all"
:max-size="50 * 1024 * 1024"
/>
</view>
</wd-cell-group>
@@ -465,12 +490,14 @@ const templateCollapse = ref<string[]>(['template1'])
</view>
</template>
<view class="step-content">
<sar-upload
v-model="subFileList2"
<wd-upload
ref="subFileListRef2"
v-model:file-list="subFileList2"
:auto-upload="false"
:before-remove="handleBeforeRemove"
multiple
:before-read="beforeRead"
:before-remove="beforeRemove"
@item-click="onItemClick"
accept="all"
:max-size="50 * 1024 * 1024"
/>
</view>
</wd-cell-group>
@@ -48,6 +48,10 @@ export function deleteFileApi(subid) {
})
}
export function deleteFilesApi(subids: any[]) {
return httpPost(`file/deletes`, subids)
}
export function deleteAliyunFileApi(fileUrl) {
return httpPost(`/file/deleteAliyunFile`, {
bucketName: aliyunBucket,
+45 -18
View File
@@ -126,35 +126,63 @@ export function formatFileSize(size: number): string {
/** 获取文件名:从 URL 提取最后一段 */
function getFileName(fileUrl: string) {
return decodeURIComponent(fileUrl.split('/').pop() || '未命名文件')
return decodeURIComponent(
fileUrl.split('/').pop() || '未命名文件',
)
}
/** 获取文件后缀 */
export function getFileExtension(fileUrl: string) {
const clean = fileUrl.split('?')[0].split('#')[0].trim()
/** 获取文件后缀(根据文件名) */
export function getFileExtension(fileName: string) {
const clean = fileName
.split('?')[0]
.split('#')[0]
.trim()
const lastDot = clean.lastIndexOf('.')
return lastDot === -1 ? '' : clean.substring(lastDot + 1).toLowerCase()
return lastDot === -1
? ''
: clean.substring(lastDot + 1).toLowerCase()
}
/** 文件类型判断 */
export function isImage(fileUrl: string) {
return ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp'].includes(getFileExtension(fileUrl))
/** 文件类型判断(根据文件名) */
export function isImage(fileName: string) {
return [
'jpg',
'jpeg',
'png',
'gif',
'webp',
'bmp',
].includes(getFileExtension(fileName))
}
export function isVideo(fileUrl: string) {
return ['mp4', 'avi', 'mov', 'mkv', 'flv', 'wmv', '3gp'].includes(getFileExtension(fileUrl))
export function isVideo(fileName: string) {
return [
'mp4',
'avi',
'mov',
'mkv',
'flv',
'wmv',
'3gp',
].includes(getFileExtension(fileName))
}
/** =================== 下载入口 =================== */
export function downloadFile(fileUrl: string, fileName?: string) {
const type: 'image' | 'video' | 'file' = isImage(fileUrl)
? 'image'
: isVideo(fileUrl)
? 'video'
: 'file'
export function downloadFile(
fileUrl: string,
fileName?: string,
) {
const finalName = fileName ?? getFileName(fileUrl)
const type: 'image' | 'video' | 'file'
= isImage(finalName)
? 'image'
: isVideo(finalName)
? 'video'
: 'file'
if (isH5) {
downloadH5(fileUrl, finalName)
}
@@ -165,7 +193,6 @@ export function downloadFile(fileUrl: string, fileName?: string) {
downloadApp(fileUrl, type, finalName)
}
}
/** =================== H5 下载 =================== */
async function downloadH5(fileUrl: string, fileName: string) {
try {