|
|
@@ -0,0 +1,344 @@
|
|
|
+<template>
|
|
|
+ <div class="container">
|
|
|
+ <div class="group-scan">
|
|
|
+ <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="express-input-box">
|
|
|
+ <div class="express-input-text">
|
|
|
+ <div>快递单号</div>
|
|
|
+ </div>
|
|
|
+ <van-field
|
|
|
+ class="express-input"
|
|
|
+ ref="expressNoRef"
|
|
|
+ :style="expressNo !== '' ? 'border: 2px solid #07c160' : ''"
|
|
|
+ clearable
|
|
|
+ v-model="expressNo"
|
|
|
+ placeholder="扫描面单条码后自动填充,也可手动输入"
|
|
|
+ />
|
|
|
+ </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"
|
|
|
+ :before-read="beforeReadImage"
|
|
|
+ :after-read="afterReadImage"
|
|
|
+ preview-full-image
|
|
|
+ capture="camera"
|
|
|
+ accept="image/*"
|
|
|
+ >
|
|
|
+ <template #preview-cover>
|
|
|
+ <div class="preview-cover">
|
|
|
+ <van-icon name="photo" />
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </van-uploader>
|
|
|
+
|
|
|
+ <div class="upload-footer">
|
|
|
+ <van-button
|
|
|
+ type="primary"
|
|
|
+ block
|
|
|
+ :loading="submitting"
|
|
|
+ :disabled="!expressNo.trim()"
|
|
|
+ @click="handleSubmit"
|
|
|
+ >
|
|
|
+ {{ submitting ? '提交中...' : '提交' }}
|
|
|
+ </van-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup>
|
|
|
+import { ref } from 'vue'
|
|
|
+import { showNotify, showFailToast, showLoadingToast, closeToast } from 'vant'
|
|
|
+import { submitProcessingGroupScan } from '@/api/processing/index'
|
|
|
+import { getHeader, goBack, scanSuccess, scanError } from '@/utils/android'
|
|
|
+import Quagga from '@ericblade/quagga2'
|
|
|
+
|
|
|
+try {
|
|
|
+ getHeader()
|
|
|
+} catch (error) {
|
|
|
+ console.log(error)
|
|
|
+}
|
|
|
+
|
|
|
+const uploadImages = ref([])
|
|
|
+const submitting = ref(false)
|
|
|
+const expressNo = ref('')
|
|
|
+const expressNoRef = ref(null)
|
|
|
+const recognizing = 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
|
|
|
+ }
|
|
|
+ if (!/^image\//.test(f.type)) {
|
|
|
+ showFailToast('请上传图片文件')
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return true
|
|
|
+}
|
|
|
+
|
|
|
+const recognizeBarcode = async (file) => {
|
|
|
+ return new Promise((resolve) => {
|
|
|
+ try {
|
|
|
+ recognizing.value = true
|
|
|
+ const img = new Image()
|
|
|
+ const url = URL.createObjectURL(file)
|
|
|
+
|
|
|
+ img.onload = () => {
|
|
|
+ try {
|
|
|
+ const canvas = document.createElement('canvas')
|
|
|
+ const ctx = canvas.getContext('2d')
|
|
|
+ canvas.width = img.width
|
|
|
+ canvas.height = img.height
|
|
|
+ ctx.drawImage(img, 0, 0)
|
|
|
+
|
|
|
+ Quagga.decodeSingle(
|
|
|
+ {
|
|
|
+ decoder: {
|
|
|
+ readers: ['code_128_reader'],
|
|
|
+ },
|
|
|
+ locate: true,
|
|
|
+ src: canvas.toDataURL(),
|
|
|
+ numOfWorkers: 0,
|
|
|
+ },
|
|
|
+ (result) => {
|
|
|
+ if (result && result.codeResult && result.codeResult.code) {
|
|
|
+ const barcodeText = result.codeResult.code
|
|
|
+ URL.revokeObjectURL(url)
|
|
|
+ recognizing.value = false
|
|
|
+ scanSuccess()
|
|
|
+ resolve(barcodeText)
|
|
|
+ } else {
|
|
|
+ Quagga.decodeSingle(
|
|
|
+ {
|
|
|
+ decoder: {
|
|
|
+ readers: [
|
|
|
+ 'code_128_reader',
|
|
|
+ 'ean_reader',
|
|
|
+ 'ean_8_reader',
|
|
|
+ 'code_39_reader',
|
|
|
+ 'code_39_vin_reader',
|
|
|
+ 'codabar_reader',
|
|
|
+ 'upc_reader',
|
|
|
+ 'upc_e_reader',
|
|
|
+ 'i2of5_reader',
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ locate: true,
|
|
|
+ src: canvas.toDataURL(),
|
|
|
+ numOfWorkers: 0,
|
|
|
+ },
|
|
|
+ (fallbackResult) => {
|
|
|
+ URL.revokeObjectURL(url)
|
|
|
+ recognizing.value = false
|
|
|
+ if (fallbackResult && fallbackResult.codeResult && fallbackResult.codeResult.code) {
|
|
|
+ scanSuccess()
|
|
|
+ resolve(fallbackResult.codeResult.code)
|
|
|
+ } else {
|
|
|
+ scanError()
|
|
|
+ resolve(null)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ )
|
|
|
+ }
|
|
|
+ }
|
|
|
+ )
|
|
|
+ } catch (error) {
|
|
|
+ URL.revokeObjectURL(url)
|
|
|
+ recognizing.value = false
|
|
|
+ scanError()
|
|
|
+ resolve(null)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ img.onerror = () => {
|
|
|
+ URL.revokeObjectURL(url)
|
|
|
+ recognizing.value = false
|
|
|
+ scanError()
|
|
|
+ resolve(null)
|
|
|
+ }
|
|
|
+
|
|
|
+ img.src = url
|
|
|
+ } catch (error) {
|
|
|
+ recognizing.value = false
|
|
|
+ scanError()
|
|
|
+ resolve(null)
|
|
|
+ }
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+const afterReadImage = async (file) => {
|
|
|
+ const files = Array.isArray(file) ? file : [file]
|
|
|
+ for (const fileItem of files) {
|
|
|
+ const originalFile = fileItem.file
|
|
|
+ if (!originalFile) continue
|
|
|
+
|
|
|
+ showNotify({ type: 'primary', message: '正在识别条码...' })
|
|
|
+ const barcodeResult = await recognizeBarcode(originalFile)
|
|
|
+ if (barcodeResult) {
|
|
|
+ expressNo.value = barcodeResult
|
|
|
+ showNotify({
|
|
|
+ type: 'success',
|
|
|
+ message: `识别到快递单号: ${barcodeResult}`,
|
|
|
+ })
|
|
|
+ } else {
|
|
|
+ showNotify({
|
|
|
+ type: 'warning',
|
|
|
+ message: '未识别到条码,请确保图片清晰或手动输入',
|
|
|
+ })
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const resetForm = () => {
|
|
|
+ expressNo.value = ''
|
|
|
+ uploadImages.value = []
|
|
|
+}
|
|
|
+
|
|
|
+const handleSubmit = async () => {
|
|
|
+ const deliveryNo = expressNo.value.trim()
|
|
|
+ if (!deliveryNo) {
|
|
|
+ showFailToast('请先扫描或输入快递单号')
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ submitting.value = true
|
|
|
+ showLoadingToast({ message: '提交中...', forbidClick: true })
|
|
|
+
|
|
|
+ try {
|
|
|
+ const response = await submitProcessingGroupScan(deliveryNo)
|
|
|
+ closeToast()
|
|
|
+ if (response.code === 200) {
|
|
|
+ showNotify({ type: 'success', message: '提交成功' })
|
|
|
+ resetForm()
|
|
|
+ } else {
|
|
|
+ showNotify({ type: 'danger', message: response.message || '提交失败' })
|
|
|
+ }
|
|
|
+ } catch (err) {
|
|
|
+ closeToast()
|
|
|
+ showNotify({
|
|
|
+ type: 'danger',
|
|
|
+ message: '提交异常: ' + (err.message || '未知错误'),
|
|
|
+ })
|
|
|
+ } finally {
|
|
|
+ submitting.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped lang="sass">
|
|
|
+.container
|
|
|
+ height: 100vh
|
|
|
+ background: #f5f5f5
|
|
|
+
|
|
|
+.group-scan
|
|
|
+ height: 100%
|
|
|
+
|
|
|
+.top
|
|
|
+ height: 100%
|
|
|
+ display: flex
|
|
|
+ flex-direction: column
|
|
|
+
|
|
|
+.nav-bar
|
|
|
+ flex-shrink: 0
|
|
|
+
|
|
|
+.context
|
|
|
+ flex: 1
|
|
|
+ padding: 15px
|
|
|
+ overflow-y: auto
|
|
|
+
|
|
|
+.express-input-box
|
|
|
+ margin-bottom: 20px
|
|
|
+ padding: 10px 5px
|
|
|
+ .express-input-text
|
|
|
+ display: flex
|
|
|
+ justify-content: space-between
|
|
|
+ align-items: center
|
|
|
+ font-size: 18px
|
|
|
+ font-weight: bold
|
|
|
+ margin: 10px 0
|
|
|
+ padding: 5px 0
|
|
|
+ .express-input
|
|
|
+ background: #eff0f2
|
|
|
+ padding: 10px 20px
|
|
|
+ font-size: 20px
|
|
|
+ border: 2px solid #0077ff
|
|
|
+ font-weight: 500
|
|
|
+
|
|
|
+.upload-section
|
|
|
+ background: #fff
|
|
|
+ padding: 20px
|
|
|
+ border-radius: 8px
|
|
|
+
|
|
|
+.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
|
|
|
+
|
|
|
+.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>
|