116 lines
2.6 KiB
Vue
116 lines
2.6 KiB
Vue
<template>
|
|
<view class="w-full">
|
|
<view class="flex items-center rounded-xl bg-white p-2 shadow-sm">
|
|
<input
|
|
v-model="scanValue"
|
|
class="h-11 min-w-0 flex-1 px-3 text-sm text-gray-700"
|
|
type="text"
|
|
confirm-type="done"
|
|
maxlength="128"
|
|
:focus="focusRequested"
|
|
:disabled="disabled || loading"
|
|
placeholder="扫描二维码或输入托盘号后按回车"
|
|
@confirm="handleSubmit"
|
|
@input="clearError"
|
|
/>
|
|
<button
|
|
class="ml-2 h-10 rounded-lg bg-blue-500 px-3 text-xs text-white active:opacity-80 disabled:bg-gray-300"
|
|
:disabled="disabled || loading"
|
|
@click="handleSubmit"
|
|
>
|
|
{{ loading ? "校验中" : "提交扫描" }}
|
|
</button>
|
|
</view>
|
|
<text v-if="errorMessage" class="mt-2 block text-xs text-red-500">
|
|
{{ errorMessage }}
|
|
</text>
|
|
</view>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { nextTick, onMounted, ref, watch } from "vue";
|
|
import { isDuplicateScan, normalizeScanValue } from "@/utils/palletScan";
|
|
|
|
const props = defineProps({
|
|
disabled: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
loading: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
duplicateWindowMs: {
|
|
type: Number,
|
|
default: 500,
|
|
},
|
|
});
|
|
|
|
const emit = defineEmits(["scan", "invalid", "duplicate"]);
|
|
const scanValue = ref("");
|
|
const errorMessage = ref("");
|
|
const focusRequested = ref(false);
|
|
let lastScanValue = "";
|
|
let lastScanAt = 0;
|
|
|
|
function focusInput() {
|
|
if (props.disabled || props.loading) return;
|
|
focusRequested.value = false;
|
|
nextTick(() => {
|
|
if (!props.disabled && !props.loading) {
|
|
focusRequested.value = true;
|
|
}
|
|
});
|
|
}
|
|
|
|
function clearError() {
|
|
errorMessage.value = "";
|
|
}
|
|
|
|
function handleSubmit() {
|
|
if (props.disabled || props.loading) return;
|
|
|
|
let normalizedValue;
|
|
try {
|
|
normalizedValue = normalizeScanValue(scanValue.value);
|
|
} catch (error) {
|
|
errorMessage.value = error.message;
|
|
emit("invalid", error.message);
|
|
focusInput();
|
|
return;
|
|
}
|
|
|
|
const scannedAt = Date.now();
|
|
if (
|
|
isDuplicateScan(
|
|
normalizedValue,
|
|
lastScanValue,
|
|
scannedAt,
|
|
lastScanAt,
|
|
props.duplicateWindowMs,
|
|
)
|
|
) {
|
|
scanValue.value = "";
|
|
emit("duplicate", normalizedValue);
|
|
focusInput();
|
|
return;
|
|
}
|
|
|
|
lastScanValue = normalizedValue;
|
|
lastScanAt = scannedAt;
|
|
scanValue.value = "";
|
|
errorMessage.value = "";
|
|
emit("scan", normalizedValue);
|
|
focusInput();
|
|
}
|
|
|
|
onMounted(focusInput);
|
|
|
|
watch(
|
|
() => [props.disabled, props.loading],
|
|
([disabled, loading]) => {
|
|
if (!disabled && !loading) focusInput();
|
|
},
|
|
);
|
|
</script>
|