|
|
@@ -25,6 +25,11 @@
|
|
|
</div>
|
|
|
|
|
|
<div class="context">
|
|
|
+ <div class="scan-tips">
|
|
|
+ <van-icon name="scan" size="20" />
|
|
|
+ <span>请扫描或手动输入快递单号</span>
|
|
|
+ </div>
|
|
|
+
|
|
|
<div class="express-input-box">
|
|
|
<div class="express-input-text">
|
|
|
<div>快递单号</div>
|
|
|
@@ -35,43 +40,21 @@
|
|
|
:style="expressNo !== '' ? 'border: 2px solid #07c160' : ''"
|
|
|
clearable
|
|
|
v-model="expressNo"
|
|
|
- placeholder="扫描面单条码后自动填充,也可手动输入"
|
|
|
+ placeholder="扫描后自动填充,也可手动输入"
|
|
|
+ @keyup.enter="handleSubmit"
|
|
|
/>
|
|
|
</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/*"
|
|
|
+ <div class="action-section">
|
|
|
+ <van-button
|
|
|
+ type="primary"
|
|
|
+ block
|
|
|
+ :loading="submitting"
|
|
|
+ :disabled="!expressNo.trim()"
|
|
|
+ @click="handleSubmit"
|
|
|
>
|
|
|
- <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>
|
|
|
+ {{ submitting ? '提交中...' : '提交' }}
|
|
|
+ </van-button>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
@@ -80,155 +63,26 @@
|
|
|
</template>
|
|
|
|
|
|
<script setup>
|
|
|
-import { ref } from 'vue'
|
|
|
+import { onMounted, onUnmounted, 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'
|
|
|
+import { androidFocus, getHeader, goBack, scanSuccess, scanError } from '@/utils/android'
|
|
|
+import { closeListener, openListener, scanInit } from '@/utils/keydownListener'
|
|
|
+import { barcodeToUpperCase } from '@/utils/dataType'
|
|
|
|
|
|
try {
|
|
|
getHeader()
|
|
|
+ androidFocus()
|
|
|
} 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 () => {
|
|
|
@@ -237,6 +91,7 @@ const handleSubmit = async () => {
|
|
|
showFailToast('请先扫描或输入快递单号')
|
|
|
return
|
|
|
}
|
|
|
+ if (submitting.value) return
|
|
|
|
|
|
submitting.value = true
|
|
|
showLoadingToast({ message: '提交中...', forbidClick: true })
|
|
|
@@ -260,6 +115,25 @@ const handleSubmit = async () => {
|
|
|
submitting.value = false
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+const _handlerScan = (code) => {
|
|
|
+ const barcode = barcodeToUpperCase(code?.trim())
|
|
|
+ if (!barcode) {
|
|
|
+ scanError()
|
|
|
+ return
|
|
|
+ }
|
|
|
+ expressNo.value = barcode
|
|
|
+ scanSuccess()
|
|
|
+}
|
|
|
+
|
|
|
+onMounted(() => {
|
|
|
+ openListener()
|
|
|
+ scanInit(_handlerScan)
|
|
|
+})
|
|
|
+
|
|
|
+onUnmounted(() => {
|
|
|
+ closeListener()
|
|
|
+})
|
|
|
</script>
|
|
|
|
|
|
<style scoped lang="sass">
|
|
|
@@ -283,9 +157,26 @@ const handleSubmit = async () => {
|
|
|
padding: 15px
|
|
|
overflow-y: auto
|
|
|
|
|
|
+.scan-tips
|
|
|
+ display: flex
|
|
|
+ align-items: center
|
|
|
+ justify-content: center
|
|
|
+ margin-bottom: 20px
|
|
|
+ padding: 16px
|
|
|
+ background: #fff
|
|
|
+ border-radius: 8px
|
|
|
+ color: #666
|
|
|
+ font-size: 16px
|
|
|
+
|
|
|
+ span
|
|
|
+ margin-left: 8px
|
|
|
+
|
|
|
.express-input-box
|
|
|
margin-bottom: 20px
|
|
|
padding: 10px 5px
|
|
|
+ background: #fff
|
|
|
+ border-radius: 8px
|
|
|
+
|
|
|
.express-input-text
|
|
|
display: flex
|
|
|
justify-content: space-between
|
|
|
@@ -293,7 +184,8 @@ const handleSubmit = async () => {
|
|
|
font-size: 18px
|
|
|
font-weight: bold
|
|
|
margin: 10px 0
|
|
|
- padding: 5px 0
|
|
|
+ padding: 5px 10px 0
|
|
|
+
|
|
|
.express-input
|
|
|
background: #eff0f2
|
|
|
padding: 10px 20px
|
|
|
@@ -301,44 +193,8 @@ const handleSubmit = async () => {
|
|
|
border: 2px solid #0077ff
|
|
|
font-weight: 500
|
|
|
|
|
|
-.upload-section
|
|
|
+.action-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>
|