scanner in CustomerSelector.vue
This commit is contained in:
@@ -9,12 +9,34 @@
|
||||
>
|
||||
<div class="customer-selector">
|
||||
<div class="search-wrapper">
|
||||
<t-input
|
||||
v-model.trim="keyword"
|
||||
class="search"
|
||||
borderless
|
||||
clearable
|
||||
placeholder="搜索编码、姓名(拼音)、电话"
|
||||
<div class="search-row">
|
||||
<t-input
|
||||
v-model.trim="keyword"
|
||||
class="search"
|
||||
borderless
|
||||
clearable
|
||||
placeholder="搜索编码、姓名(拼音)、电话"
|
||||
/>
|
||||
<t-button
|
||||
v-if="props.enableScan"
|
||||
class="scan-button"
|
||||
theme="primary"
|
||||
@click="toggleScanner"
|
||||
>
|
||||
<template #icon>
|
||||
<t-icon name="scan" />
|
||||
</template>
|
||||
<span v-text="scannerVisible ? '关闭扫码' : '扫码'" />
|
||||
</t-button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="scannerVisible" class="scanner-wrapper">
|
||||
<code-scanner
|
||||
:visible="scannerVisible"
|
||||
:status-text="scanStatus"
|
||||
active-text="正在识别客户编码,请将条码或二维码移入取景框。"
|
||||
@decode="handleDecodedText"
|
||||
@status-change="handleScanStatusChange"
|
||||
/>
|
||||
</div>
|
||||
<div ref="resultListRef" class="result-list">
|
||||
@@ -34,12 +56,15 @@
|
||||
import { Scroller } from "timi-web";
|
||||
import { BottomPopup } from "timi-tdesign-mobile";
|
||||
import CustomerAPI from "@/api/CustomerAPI";
|
||||
import CodeScanner from "@/components/CodeScanner.vue";
|
||||
import CustomerList from "@/components/CustomerList.vue";
|
||||
import { useSettingStore } from "@/store/settingStore";
|
||||
import type { Customer } from "@/types/Customer";
|
||||
|
||||
type SelectionMode = "single" | "multiple";
|
||||
|
||||
interface Props {
|
||||
enableScan?: boolean;
|
||||
modelValue: string | string[];
|
||||
visible: boolean;
|
||||
selectionMode?: SelectionMode;
|
||||
@@ -47,10 +72,13 @@ interface Props {
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
enableScan: true,
|
||||
selectionMode: "single",
|
||||
storeId: ""
|
||||
});
|
||||
|
||||
const settingStore = useSettingStore();
|
||||
|
||||
const emit = defineEmits<{
|
||||
"update:modelValue": [value: string | string[]];
|
||||
"update:visible": [value: boolean];
|
||||
@@ -70,6 +98,10 @@ const draftSelectedIdList = ref<string[]>([]);
|
||||
const selectedCustomerCache = new Map<string, Customer>();
|
||||
let searchToken = 0;
|
||||
let keywordTimer: number | undefined;
|
||||
const scannerVisible = ref(false);
|
||||
const scanStatus = ref("点击扫码开始识别");
|
||||
let latestDecodedText = "";
|
||||
let scanToken = 0;
|
||||
|
||||
const isMultiple = computed(() => props.selectionMode === "multiple");
|
||||
|
||||
@@ -159,8 +191,8 @@ function toggleCustomer(item: Customer): void {
|
||||
|
||||
selectedCustomerCache.set(id, item);
|
||||
if (!isMultiple.value) {
|
||||
emit("update:modelValue", id);
|
||||
emit("change", id);
|
||||
emit("update:modelValue", id);
|
||||
emit("change", id);
|
||||
emit("update:visible", false);
|
||||
return;
|
||||
}
|
||||
@@ -173,6 +205,64 @@ function toggleCustomer(item: Customer): void {
|
||||
draftSelectedIdList.value = [...draftSelectedIdList.value, id];
|
||||
}
|
||||
|
||||
function toggleScanner(): void {
|
||||
if (!props.enableScan) {
|
||||
return;
|
||||
}
|
||||
scannerVisible.value = !scannerVisible.value;
|
||||
if (scannerVisible.value) {
|
||||
latestDecodedText = "";
|
||||
scanStatus.value = "正在启动扫码预览";
|
||||
return;
|
||||
}
|
||||
scanToken++;
|
||||
}
|
||||
|
||||
async function handleDecodedText(rawText: string): Promise<void> {
|
||||
const normalizedText = rawText.trim();
|
||||
if (!normalizedText || normalizedText === latestDecodedText) {
|
||||
return;
|
||||
}
|
||||
|
||||
latestDecodedText = normalizedText;
|
||||
const token = ++scanToken;
|
||||
await settingStore.playScanSuccessSound();
|
||||
try {
|
||||
const customer = await CustomerAPI.findByCode(normalizedText, props.storeId || undefined);
|
||||
if (token !== scanToken) {
|
||||
return;
|
||||
}
|
||||
if (!customer?.id) {
|
||||
scanStatus.value = `客户 ${normalizedText} 不存在,请先建档。`;
|
||||
return;
|
||||
}
|
||||
|
||||
selectedCustomerCache.set(customer.id, customer);
|
||||
if (!customerOptions.value.some(item => item.id === customer.id)) {
|
||||
customerOptions.value = [customer, ...customerOptions.value];
|
||||
}
|
||||
if (isMultiple.value) {
|
||||
if (!draftSelectedIdList.value.includes(customer.id)) {
|
||||
draftSelectedIdList.value = [...draftSelectedIdList.value, customer.id];
|
||||
}
|
||||
} else {
|
||||
emit("update:modelValue", customer.id);
|
||||
emit("change", customer.id);
|
||||
emit("update:visible", false);
|
||||
}
|
||||
scanStatus.value = `已识别 ${customer.code || normalizedText},已选中 ${customer.name || "未命名客户"}。`;
|
||||
scannerVisible.value = false;
|
||||
} catch {
|
||||
if (token === scanToken) {
|
||||
scanStatus.value = "客户查询失败,请重试。";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handleScanStatusChange(value: string): void {
|
||||
scanStatus.value = value;
|
||||
}
|
||||
|
||||
function cancelSelection(): void {
|
||||
syncDraftSelection();
|
||||
emit("update:visible", false);
|
||||
@@ -189,6 +279,10 @@ watch(() => props.visible, async (value) => {
|
||||
if (!value) {
|
||||
syncDraftSelection();
|
||||
keyword.value = "";
|
||||
scannerVisible.value = false;
|
||||
latestDecodedText = "";
|
||||
scanToken++;
|
||||
scanStatus.value = "点击扫码开始识别";
|
||||
window.clearTimeout(keywordTimer);
|
||||
Scroller.removeListener(scrollListenerName);
|
||||
return;
|
||||
@@ -198,6 +292,14 @@ watch(() => props.visible, async (value) => {
|
||||
await bindScrollListener();
|
||||
});
|
||||
|
||||
watch(() => props.enableScan, (value) => {
|
||||
if (value) {
|
||||
return;
|
||||
}
|
||||
scannerVisible.value = false;
|
||||
scanToken++;
|
||||
});
|
||||
|
||||
watch(() => props.storeId, async () => {
|
||||
if (!props.visible) {
|
||||
return;
|
||||
@@ -229,23 +331,39 @@ onUnmounted(() => {
|
||||
|
||||
<style scoped lang="less">
|
||||
.customer-selector {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.search-wrapper {
|
||||
z-index: 1;
|
||||
padding: 1rem 0 .5rem 0;
|
||||
padding: 1rem 0 .5rem;
|
||||
box-shadow: 0 4px 4px rgba(0, 0, 0, .05);
|
||||
|
||||
.search-row {
|
||||
gap: .5rem;
|
||||
display: flex;
|
||||
padding: 0 1rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.search {
|
||||
--td-input-vertical-padding: 8px 14px;
|
||||
|
||||
flex: 0;
|
||||
margin: 0 1rem;
|
||||
flex: 1;
|
||||
border: 1px solid var(--td-component-border);
|
||||
min-width: 0;
|
||||
border-radius: .875rem;
|
||||
}
|
||||
|
||||
.scan-button {
|
||||
--td-button-border-radius: .75rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.scanner-wrapper {
|
||||
padding: .5rem 1rem 0;
|
||||
}
|
||||
|
||||
.result-list {
|
||||
|
||||
Reference in New Issue
Block a user