|
@@ -0,0 +1,299 @@
|
|
|
|
|
+<template>
|
|
|
|
|
+ <div class="container">
|
|
|
|
|
+ <div class="photo-ocr">
|
|
|
|
|
+ <div class="top">
|
|
|
|
|
+ <div class="nav-bar">
|
|
|
|
|
+ <van-nav-bar
|
|
|
|
|
+ title="面单识别"
|
|
|
|
|
+ left-arrow
|
|
|
|
|
+ @click-left="goBack"
|
|
|
|
|
+ >
|
|
|
|
|
+ <template #left>
|
|
|
|
|
+ <van-icon name="arrow-left" size="25" />
|
|
|
|
|
+ <div
|
|
|
|
|
+ style="
|
|
|
|
|
+ color: #fff;
|
|
|
|
|
+ height: 46px;
|
|
|
|
|
+ padding-right: 20px;
|
|
|
|
|
+ line-height: 46px;
|
|
|
|
|
+ "
|
|
|
|
|
+ >
|
|
|
|
|
+ 返回
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </van-nav-bar>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="context">
|
|
|
|
|
+ <!-- 仓库信息显示 -->
|
|
|
|
|
+ <div class="warehouse-info">
|
|
|
|
|
+ <van-cell title="当前仓库" :value="warehouse" />
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 拍照上传区域 -->
|
|
|
|
|
+ <div class="upload-section">
|
|
|
|
|
+ <div class="upload-tips">
|
|
|
|
|
+ <van-icon name="photo" size="20" />
|
|
|
|
|
+ <span>请拍摄或选择面单图片进行识别</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <van-uploader
|
|
|
|
|
+ v-model="uploadImages"
|
|
|
|
|
+ :max-count="1"
|
|
|
|
|
+ :max-size="10 * 1024 * 1024"
|
|
|
|
|
+ :before-read="beforeReadImage"
|
|
|
|
|
+ :after-read="afterReadImage"
|
|
|
|
|
+ preview-full-image
|
|
|
|
|
+ capture="camera"
|
|
|
|
|
+ accept="image/*"
|
|
|
|
|
+ >
|
|
|
|
|
+ <template #preview-cover="{ file }">
|
|
|
|
|
+ <div class="preview-cover">
|
|
|
|
|
+ <van-icon name="photo" />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </van-uploader>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="upload-footer">
|
|
|
|
|
+ <van-button
|
|
|
|
|
+ type="primary"
|
|
|
|
|
+ block
|
|
|
|
|
+ :loading="uploading"
|
|
|
|
|
+ @click="submitOCR"
|
|
|
|
|
+ :disabled="!uploadImages.length"
|
|
|
|
|
+ >
|
|
|
|
|
+ {{ uploading ? '上传中...' : '上传识别' }}
|
|
|
|
|
+ </van-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<script setup>
|
|
|
|
|
+import { ref, onMounted } from 'vue'
|
|
|
|
|
+import { showNotify, showFailToast, showLoadingToast, closeToast } from 'vant'
|
|
|
|
|
+import { uploadOCRImage } from '@/api/inbound/index'
|
|
|
|
|
+import { getHeader, goBack } from '@/utils/android'
|
|
|
|
|
+import { compressImage } from '@/utils/imageCompression'
|
|
|
|
|
+import { useStore } from '@/store/modules/user'
|
|
|
|
|
+
|
|
|
|
|
+try {
|
|
|
|
|
+ getHeader()
|
|
|
|
|
+} catch (error) {
|
|
|
|
|
+ console.log(error)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const store = useStore()
|
|
|
|
|
+const warehouse = store.warehouse
|
|
|
|
|
+
|
|
|
|
|
+const uploadImages = ref([])
|
|
|
|
|
+const uploading = ref(false)
|
|
|
|
|
+
|
|
|
|
|
+// 检查图片格式和大小
|
|
|
|
|
+const beforeReadImage = (file) => {
|
|
|
|
|
+ const files = Array.isArray(file) ? file : [file]
|
|
|
|
|
+ for (const f of files) {
|
|
|
|
|
+ // 检查文件格式
|
|
|
|
|
+ if (f.name.toLowerCase().endsWith('.heic') || f.name.toLowerCase().endsWith('.heif')) {
|
|
|
|
|
+ showFailToast('不支持HEIC/HEIF格式的图片')
|
|
|
|
|
+ return false
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 检查文件类型
|
|
|
|
|
+ const isImage = /^image\//.test(f.type)
|
|
|
|
|
+ if (!isImage) {
|
|
|
|
|
+ showFailToast('请上传图片文件')
|
|
|
|
|
+ return false
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 检查文件大小 (10MB)
|
|
|
|
|
+ if (f.size > 10 * 1024 * 1024) {
|
|
|
|
|
+ showFailToast('图片大小不能超过10MB')
|
|
|
|
|
+ return false
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return true
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 图片读取完成后处理
|
|
|
|
|
+const afterReadImage = async (file) => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 处理单个文件或多个文件
|
|
|
|
|
+ const files = Array.isArray(file) ? file : [file]
|
|
|
|
|
+
|
|
|
|
|
+ for (let i = 0; i < files.length; i++) {
|
|
|
|
|
+ const fileItem = files[i]
|
|
|
|
|
+ const originalFile = fileItem.file
|
|
|
|
|
+
|
|
|
|
|
+ if (originalFile) {
|
|
|
|
|
+ // 检查文件大小,如果大于1MB则进行压缩
|
|
|
|
|
+ if (originalFile.size > 1 * 1024 * 1024) {
|
|
|
|
|
+ showNotify({
|
|
|
|
|
+ type: 'primary',
|
|
|
|
|
+ message: `正在压缩图片: ${originalFile.name} (${(originalFile.size / 1024 / 1024).toFixed(2)}MB)`
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 压缩图片到1MB以内
|
|
|
|
|
+ const compressedFile = await compressImage(originalFile, 1 * 1024 * 1024)
|
|
|
|
|
+
|
|
|
|
|
+ // 更新文件对象
|
|
|
|
|
+ fileItem.file = compressedFile
|
|
|
|
|
+ fileItem.content = URL.createObjectURL(compressedFile)
|
|
|
|
|
+
|
|
|
|
|
+ // 显示压缩结果
|
|
|
|
|
+ const compressionRatio = ((originalFile.size - compressedFile.size) / originalFile.size * 100).toFixed(1)
|
|
|
|
|
+ showNotify({
|
|
|
|
|
+ type: 'success',
|
|
|
|
|
+ message: `图片压缩完成: ${(compressedFile.size / 1024 / 1024).toFixed(2)}MB (压缩${compressionRatio}%)`
|
|
|
|
|
+ })
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('图片压缩失败:', error)
|
|
|
|
|
+ showNotify({
|
|
|
|
|
+ type: 'warning',
|
|
|
|
|
+ message: `图片压缩失败,将使用原图上传: ${error.message}`
|
|
|
|
|
+ })
|
|
|
|
|
+ // 压缩失败时保持原文件
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ console.log(`图片大小符合要求,无需压缩: ${(originalFile.size / 1024).toFixed(2)}KB`)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('处理图片时发生错误:', error)
|
|
|
|
|
+ showNotify({
|
|
|
|
|
+ type: 'danger',
|
|
|
|
|
+ message: '处理图片时发生错误: ' + error.message
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 提交OCR识别
|
|
|
|
|
+const submitOCR = async () => {
|
|
|
|
|
+ if (!uploadImages.value.length) {
|
|
|
|
|
+ showFailToast('请先选择图片')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!warehouse) {
|
|
|
|
|
+ showFailToast('未获取到仓库信息')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ uploading.value = true
|
|
|
|
|
+ const toast = showLoadingToast('识别中...')
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ const image = uploadImages.value[0]
|
|
|
|
|
+ const formData = new FormData()
|
|
|
|
|
+ formData.append('file', image.file)
|
|
|
|
|
+ formData.append('warehouse', warehouse)
|
|
|
|
|
+
|
|
|
|
|
+ const response = await uploadOCRImage(image.file, warehouse)
|
|
|
|
|
+
|
|
|
|
|
+ closeToast()
|
|
|
|
|
+
|
|
|
|
|
+ if (response.code === 200) {
|
|
|
|
|
+ showNotify({ type: 'success', message: '面单上传成功' })
|
|
|
|
|
+ // 上传成功后重置表单
|
|
|
|
|
+ uploadImages.value = []
|
|
|
|
|
+ } else {
|
|
|
|
|
+ showNotify({ type: 'danger', message: response.message || '上传失败' })
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ closeToast()
|
|
|
|
|
+ showNotify({ type: 'danger', message: '识别过程发生异常: ' + (err.message || '未知错误') })
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ uploading.value = false
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+onMounted(() => {
|
|
|
|
|
+ // 页面加载时的初始化操作
|
|
|
|
|
+ console.log('面单识别页面加载完成')
|
|
|
|
|
+})
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<style scoped lang="sass">
|
|
|
|
|
+.container
|
|
|
|
|
+ height: 100vh
|
|
|
|
|
+ background: #f5f5f5
|
|
|
|
|
+
|
|
|
|
|
+.photo-ocr
|
|
|
|
|
+ height: 100%
|
|
|
|
|
+
|
|
|
|
|
+.top
|
|
|
|
|
+ height: 100%
|
|
|
|
|
+ display: flex
|
|
|
|
|
+ flex-direction: column
|
|
|
|
|
+
|
|
|
|
|
+.nav-bar
|
|
|
|
|
+ flex-shrink: 0
|
|
|
|
|
+
|
|
|
|
|
+.context
|
|
|
|
|
+ flex: 1
|
|
|
|
|
+ padding: 15px
|
|
|
|
|
+ overflow-y: auto
|
|
|
|
|
+
|
|
|
|
|
+.warehouse-info
|
|
|
|
|
+ margin-bottom: 20px
|
|
|
|
|
+ background: #fff
|
|
|
|
|
+ border-radius: 8px
|
|
|
|
|
+ overflow: hidden
|
|
|
|
|
+
|
|
|
|
|
+.upload-section
|
|
|
|
|
+ background: #fff
|
|
|
|
|
+ padding: 20px
|
|
|
|
|
+ border-radius: 8px
|
|
|
|
|
+ margin-bottom: 20px
|
|
|
|
|
+
|
|
|
|
|
+.upload-tips
|
|
|
|
|
+ display: flex
|
|
|
|
|
+ align-items: center
|
|
|
|
|
+ justify-content: center
|
|
|
|
|
+ margin-bottom: 20px
|
|
|
|
|
+ color: #666
|
|
|
|
|
+ font-size: 16px
|
|
|
|
|
+
|
|
|
|
|
+ span
|
|
|
|
|
+ margin-left: 8px
|
|
|
|
|
+
|
|
|
|
|
+.upload-footer
|
|
|
|
|
+ margin-top: 20px
|
|
|
|
|
+
|
|
|
|
|
+.result-section
|
|
|
|
|
+ background: #fff
|
|
|
|
|
+ padding: 20px
|
|
|
|
|
+ border-radius: 8px
|
|
|
|
|
+
|
|
|
|
|
+.result-actions
|
|
|
|
|
+ margin-top: 20px
|
|
|
|
|
+
|
|
|
|
|
+.preview-cover
|
|
|
|
|
+ position: absolute
|
|
|
|
|
+ bottom: 0
|
|
|
|
|
+ box-sizing: border-box
|
|
|
|
|
+ width: 100%
|
|
|
|
|
+ padding: 4px
|
|
|
|
|
+ color: #fff
|
|
|
|
|
+ font-size: 12px
|
|
|
|
|
+ text-align: center
|
|
|
|
|
+ background: rgba(0, 0, 0, 0.3)
|
|
|
|
|
+
|
|
|
|
|
+:deep(.van-uploader__upload)
|
|
|
|
|
+ width: 100px
|
|
|
|
|
+ height: 100px
|
|
|
|
|
+ margin: 0 auto
|
|
|
|
|
+ display: block
|
|
|
|
|
+
|
|
|
|
|
+:deep(.van-uploader__preview-image)
|
|
|
|
|
+ width: 100px
|
|
|
|
|
+ height: 100px
|
|
|
|
|
+ margin: 0 auto
|
|
|
|
|
+</style>
|