This commit is contained in:
oneao committed 2026-03-30 17:27:19 +08:00
1 parent f9eaacf02a
commit 69eda8b8ad
27 files changed
+2797 -616

No files matched your search

@@ -0,0 +1,468 @@
<script lang="ts" setup>
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 { useUserStore } from '@/store'
import { buildPubicUrl, downloadFile, getFileExtension, imageCompress, isImage } from '@/utils/file'
const userStore = useUserStore()
const form = ref<any>({
b_id: 0,
b_container_no: '',
b_container_img: '',
})
const subFileList = ref([])
onLoad((options) => {
const id = options.id
if (id) {
form.value.b_id = id
loadMainData()
}
})
async function loadMainData() {
toast.loading('正在初始化中...', {
overlay: true,
})
try {
const orgUrl = await getOrgBaseUrl(userStore.userInfo.orgid)
// 并发请求(提升性能)
const [res, fileRes] = await Promise.all([
loadDataApi({
viewName: 'v_container',
searchCondition: `b_id = '${form.value.b_id}'`,
}),
loadDataApi({
viewName: 'bf_file',
searchCondition: `father = '${form.value.b_id}' AND mx_cate_id = '1'`,
}),
])
// ================= 主表 =================
if (res.success && Array.isArray(res.data) && res.data.length > 0) {
const mainData = res.data[0]
if (mainData?.b_container_img) {
mainData.b_container_img = buildPubicUrl(
orgUrl,
mainData.b_container_img,
)
}
form.value = mainData
}
else {
console.warn('未获取到主数据')
}
// ================= 子表 =================
if (fileRes.success) {
const files = (fileRes.data || []).map(file => ({
...file,
mx_mapfilename: buildPubicUrl(orgUrl, file.mx_mapfilename),
name: file.mx_filename,
url: buildPubicUrl(orgUrl, file.mx_mapfilename),
}))
subFileList.value = files
}
}
catch (err) {
console.error('加载失败:', err)
}
finally {
toast.hide()
}
}
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 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('集装箱号图片不能为空')
return
}
if (form.value.b_container_no === '') {
toast.fail('集装箱号不能为空')
return
}
if (!validateContainerNo(form.value.b_container_no)) {
toast.fail('集装箱号不符合规则,请检查')
return
}
toast.loading('正在保存中 . . .', {
overlay: true,
transparent: false,
})
const fileInserts = []
// 子表
for (let index = 0; index < subFileList.value.length; index++) {
const fileItem = subFileList.value[index]
if (fileItem.subid) {
// 已上传的文件,无需重复上传
continue
}
const res = await uploadContainerFileApi(fileItem.file.path, {
jobId: form.value.b_id,
containerNo: form.value.b_container_no,
fileName: fileItem.name,
})
const maxIndexNumber = Math.max(
0,
...subFileList.value.map(item => item.mx_xh || 0),
)
if (res.success) {
const indexNumber = maxIndexNumber + 1
fileInserts.push({
subid: res.data.uid,
father: form.value.b_id,
mx_xh: indexNumber,
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: '1',
})
fileItem.subid = res.data.uid
}
}
if (fileInserts.length === 0) {
toast.success('保存成功')
return
}
const reqData = [
{
tableName: 'bf_file',
keyField: 'subid',
inserts: fileInserts,
updates: [],
deletes: [],
},
]
saveObjectApi(reqData).then((res) => {
if (res.success) {
toast.success('保存成功')
}
})
}
// 集装箱号校验
function validateContainerNo(containerNo) {
// 正则表达式: 4个字母 + 7个数字
const pattern = /^[A-Z]{4}\d{7}$/i
// 使用正则进行匹配
if (pattern.test(containerNo)) {
return true
}
else {
return false
}
}
</script>
<template>
<view class="g3-container">
<view class="g3-content">
<wd-cell-group custom-class="group" title="集装箱号(OCR识别)" border use-slot>
<template #title>
<view style="display:flex;align-items: center;">
<sar-icon family="iconfont" name="iconyuanbanben_OCR" size="38rpx" color="var(--sar-primary)" style="margin-right: 16rpx;" />
<text>
箱门照(OCR识别)
</text>
</view>
</template>
<view class="step-content box">
<view class="box-main">
<view class="box-image">
<wd-img
:src="form.b_container_img"
width="100%"
height="100%"
:enable-preview="true"
/>
</view>
</view>
<view class="box-input">
<view class="header">
<view class="label-container">
<view>集装箱号</view>
<view class="required-asterisk">
*
</view>
</view>
<view class="format">
格式: 4字母 + 7数字
</view>
</view>
<sar-input
v-model="form.b_container_no"
disabled
placeholder="请输入箱号"
/>
</view>
</view>
</wd-cell-group>
<wd-cell-group custom-class="group" border use-slot style="margin-top: 32rpx;">
<template #title>
<view style="display:flex;align-items: center;">
<sar-icon family="iconfont" name="tupian" size="38rpx" color="var(--sar-primary)" style="margin-right: 16rpx;" />
<text>
其他图片
</text>
</view>
</template>
<view class="step-content">
<sar-upload
v-model="subFileList"
multiple
:before-read="beforeRead"
:before-remove="beforeRemove"
@item-click="onItemClick"
@remove="onRemove"
/>
</view>
</wd-cell-group>
</view>
<view class="g3-footer">
<view class="footer-main">
<sar-button round root-style="width:100%" icon-family="iconfont" icon="fasongtijiao" @click="handleConfirm">
保存
</sar-button>
</view>
</view>
</view>
</template>
<style lang="scss" scoped>
page {
background-color: #f8f9fa;
}
.g3-result {
height: 100%;
display: flex;
align-items: center;
justify-content: center;
.result-tip {
font-size: 36rpx;
}
.container-no {
font-weight: 600;
color: #4caf50;
}
}
.g3-container {
display: flex;
flex-direction: column;
height: 100%;
.g3-content {
flex: 1;
overflow-y: auto;
padding: 16rpx 0rpx;
}
.g3-footer {
width: 100%;
.footer-main {
padding: 16rpx;
}
}
}
.step {
margin-bottom: 32rpx;
.step-header {
display: flex;
align-items: center;
margin-bottom: 24rpx;
}
}
.step-content {
padding: 32rpx;
}
.box {
display: flex;
flex-direction: column;
.box-main {
height: 326rpx;
}
.box-placeholder {
height: 100%;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
background: var(--sar-upload-select-bg);
border-radius: var(--sar-upload-select-border-radius);
.placeholder-text {
color: #999;
font-size: 28rpx;
margin-top: 8rpx;
}
}
.box-image {
width: 100%;
height: 100%;
position: relative;
.delete-btn {
position: absolute;
top: 12rpx;
right: 12rpx;
width: 44rpx;
height: 44rpx;
border-radius: 50%;
background: rgba(0, 0, 0, 0.5);
color: #fff;
font-size: 28rpx;
font-weight: bold;
display: flex;
align-items: center;
justify-content: center;
z-index: 10;
}
}
.box-input {
margin-top: 16rpx;
display: flex;
flex-direction: column;
}
.header {
display: flex;
align-items: center;
justify-content: space-between;
font-size: 28rpx;
margin-bottom: 12rpx;
width: 100%;
.label-container {
position: relative;
}
.required-asterisk {
position: absolute;
top: -5rpx;
right: -12rpx;
color: red;
font-size: 24rpx;
}
.format {
color: #999;
font-size: 24rpx;
}
.tip {
color: #999;
}
}
}
.submit-btn {
width: 100%;
height: 72rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 999px;
font-size: 30rpx;
font-weight: 600;
color: #fff;
background: linear-gradient(135deg, #3a6ee8, #5c8df6);
box-shadow: 0 6rpx 12rpx rgba(58, 110, 232, 0.18);
transition: all 0.15s;
}
.submit-btn:active {
transform: scale(0.98);
box-shadow: 0 3rpx 6rpx rgba(58, 110, 232, 0.15);
}
</style>
@@ -1,9 +1,8 @@
<script lang="ts" setup>
import type { UploadFile, UploadFileItem } from 'sard-uniapp'
import { dialog, isImageUrl, isVideoUrl, toast } from 'sard-uniapp'
import { aliyunpluploadApi, deleteAliyunFileApi, deleteFileApi, getUniqueIdApi, recognizeOcrBoxApi, saveObjectApi, uploadTempFileApi } from '@/request/api'
import { getUniqueIdApi, recognizeOcrBoxApi, saveObjectApi, uploadContainerFileApi, uploadTempFileApi } from '@/request/api'
import { downloadFile, getFileExtension, imageCompress, isImage } from '@/utils/file'
import { uploadToOss } from '@/utils/oss'
const boxImage = ref(null)
@@ -14,8 +13,8 @@ const showResult = ref(false)
const form = ref({
b_id: 0,
containerNo: '',
containerImageUrl: '',
b_container_no: '',
b_container_img: '',
ocrScore: 0.00,
ocrNo: '',
})
@@ -53,11 +52,14 @@ function handleChooseBox() {
})
imageCompress(tempFile.path).then((compressRes) => {
console.log(compressRes)
uploadTempFileApi(compressRes.path, tempFile.name).then((res) => {
form.value.b_container_img = compressRes.path
uploadTempFileApi(compressRes.path, {
fileName: tempFile.name,
}).then((res) => {
if (res.success) {
recognizeOcrBoxApi({
fileUrl: res.data.tempUrl,
fileUrl: res.data.url,
}).then((ocrRes) => {
if (ocrRes.success) {
toast.success('识别成功,请检查箱号是否正确')
@@ -65,7 +67,7 @@ function handleChooseBox() {
const score = Number(ocrRes?.data?.score ?? 0)
form.value.ocrScore = Math.floor(score * 100) / 100
form.value.containerNo = ocrRes.data.boxNumber
form.value.b_container_no = ocrRes.data.boxNumber
form.value.ocrNo = ocrRes.data.boxNumber
isError.value = false
@@ -88,29 +90,17 @@ const computedOcrScore = computed(() => {
function resetForm() {
boxImage.value = null
form.value.containerImageUrl = ''
form.value.containerNo = ''
form.value.b_container_no = ''
form.value.ocrNo = ''
form.value.ocrScore = 0.00
showResult.value = false
isError.value = false
ocrFinish.value = false
}
function handleDeleteBoxImage() {
if (!form.value.containerImageUrl) {
resetForm()
return
}
toast.loading('正在删除中 . . .', {
overlay: true,
transparent: false,
})
deleteAliyunFileApi(form.value.containerImageUrl).then((res) => {
if (res.success) {
resetForm()
toast.success('删除成功')
}
})
resetForm()
}
function onItemClick(fileItem: UploadFileItem) {
@@ -136,9 +126,7 @@ function beforeRead(file: UploadFile): boolean | Promise<UploadFile> {
})
}
function beforeRemove(index: number, fileItem: UploadFileItem): boolean | Promise<void> {
console.log(fileItem)
function beforeRemove(_index: number, _fileItem: UploadFileItem): boolean | Promise<void> {
return new Promise((resolve, reject) => {
dialog.confirm({
title: '提示',
@@ -150,28 +138,18 @@ function beforeRemove(index: number, fileItem: UploadFileItem): boolean | Promis
})
}
function onRemove(index: number, item: UploadFileItem) {
if (item.status === 'done') {
deleteAliyunFileApi(item.url).then((res) => {
if (res.success) {
toast.success('删除成功')
}
})
}
}
async function handleConfirm() {
if (form.value.containerImageUrl === '') {
if (form.value.b_container_img === '') {
toast.fail('集装箱号图片不能为空')
return
}
if (form.value.containerNo === '') {
if (form.value.b_container_no === '') {
toast.fail('集装箱号不能为空')
return
}
if (!validateContainerNo(form.value.containerNo)) {
if (!validateContainerNo(form.value.b_container_no)) {
toast.fail('集装箱号不符合规则,请检查')
return
}
@@ -181,82 +159,88 @@ 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 = []
// 主表
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 mainTable = {
// b_id: form.value.b_id,
// b_container_no: form.value.containerNo,
// b_container_img: form.value.containerImageUrl,
// b_ocr_no: form.value.ocrNo,
// b_ocr_score: form.value.ocrScore || 0.00,
// }
fileInserts.push({
subid: res.data.uid,
father: form.value.b_id,
mx_xh: 0,
mx_filename: res.data.fileName,
mx_filesize: boxImage.value.size,
mx_fileext: getFileExtension(boxImage.value.name),
mx_mapfilename: res.data.path,
mx_moduleid: 'b_container',
mx_cate_id: '0',
})
}
})
// const fileInserts = []
// 子表
for (let index = 0; index < subFileList.value.length; index++) {
const fileItem = subFileList.value[index]
// const containerNo = form.value.containerNo
const res = await uploadContainerFileApi(fileItem.file.path, {
jobId: form.value.b_id,
containerNo: form.value.b_container_no,
fileName: fileItem.name,
})
// subFileList.value.forEach(async (fileItem, index) => {
// const uniqueRes = await getUniqueIdApi()
// const indexNumber = index + 2
if (res.success) {
const indexNumber = index + 2
// fileInserts.push({
// subid: uniqueRes.data,
// father: form.value.b_id,
// mx_xh: indexNumber,
// mx_filename: buildFileName(containerNo, indexNumber, fileItem.file.name),
// mx_filesize: fileItem.file.size,
// mx_fileext: getFileExtension(fileItem.file.name),
// mx_mapfilename: fileItem.url,
// mx_moduleid: 'b_container',
// mx_cate_id: '1',
// })
// })
fileInserts.push({
subid: res.data.uid,
father: form.value.b_id,
mx_xh: indexNumber,
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: '1',
})
}
}
// const uniqueRes = await getUniqueIdApi()
const reqData = [
{
tableName: 'b_container',
keyField: 'b_id',
inserts: [mainTable],
updates: [],
deletes: [],
},
{
tableName: 'bf_file',
keyField: 'subid',
inserts: fileInserts,
updates: [],
deletes: [],
},
]
// fileInserts.push({
// subid: uniqueRes.data,
// father: form.value.b_id,
// mx_xh: 0,
// mx_filename: buildFileName(containerNo, 1, boxImage.value.name),
// mx_filesize: boxImage.value.size,
// mx_fileext: getFileExtension(boxImage.value.name),
// mx_mapfilename: form.value.containerImageUrl,
// mx_moduleid: 'b_container',
// mx_cate_id: '0',
// })
// const reqData = [
// {
// tableName: 'b_container',
// keyField: 'b_id',
// inserts: [mainTable],
// updates: [],
// deletes: [],
// },
// {
// tableName: 'bf_file',
// keyField: 'subid',
// inserts: fileInserts,
// updates: [],
// deletes: [],
// },
// ]
// saveObjectApi(reqData).then((res) => {
// if (res.success) {
// toast.success('保存成功')
// tipContainerNo.value = form.value.containerNo
// showResult.value = true
// }
// })
}
function buildFileName(containerNo, index, fileName) {
const formattedIndex = String(index).padStart(2, '0')
return `${containerNo}_${formattedIndex}.${fileName.split('.').pop()}`
saveObjectApi(reqData).then((res) => {
if (res.success) {
toast.success('保存成功')
tipContainerNo.value = form.value.b_container_no
showResult.value = true
}
})
}
// 集装箱号校验
@@ -302,7 +286,7 @@ async function continueAdd() {
<view style="display:flex;align-items: center;">
<sar-icon family="iconfont" name="iconyuanbanben_OCR" size="38rpx" color="var(--sar-primary)" style="margin-right: 16rpx;" />
<text>
集装箱号(OCR识别)
箱门照(OCR识别)
</text>
</view>
</template>
@@ -367,7 +351,7 @@ async function continueAdd() {
</view>
</view>
<sar-input
v-model="form.containerNo"
v-model="form.b_container_no"
placeholder="请输入箱号"
/>
</view>
@@ -390,7 +374,6 @@ async function continueAdd() {
:before-read="beforeRead"
:before-remove="beforeRemove"
@item-click="onItemClick"
@remove="onRemove"
/>
</view>
</wd-cell-group>