From 8e77b114f9c321f8a9c6aed3c4a011f50ac7315c Mon Sep 17 00:00:00 2001 From: Timi Date: Wed, 9 Sep 2026 17:49:37 +0800 Subject: [PATCH] scanner in CustomerSelector.vue --- src/api/CustomerAPI.ts | 4 +- src/assets/styles/global.less | 17 + src/components/CustomerSelector.vue | 142 +++++- src/pages/customer/CustomerDataCharts.vue | 117 ++--- src/pages/customer/CustomerEdit.vue | 75 +--- src/pages/customer/CustomerInviteeList.vue | 1 + src/pages/customer/CustomerList.vue | 73 +++- src/pages/event/EventRecordEdit.vue | 1 + src/pages/event/EventRecordList.vue | 41 +- .../fulfillment/OrderFulfillmentList.vue | 410 ++++++++++++++++-- src/pages/package/PackagePurchase.vue | 1 + src/pages/point/PointAccountList.vue | 47 +- src/pages/point/PointAdjust.vue | 78 +--- src/pages/point/PointLedgerList.vue | 117 ++--- .../salesOrder/ConsumptionRecordList.vue | 15 +- src/pages/salesOrder/SalesOrderCreate.vue | 2 +- src/pages/salesOrder/SalesOrderList.vue | 73 +--- 17 files changed, 804 insertions(+), 410 deletions(-) diff --git a/src/api/CustomerAPI.ts b/src/api/CustomerAPI.ts index 1268937..b766b19 100644 --- a/src/api/CustomerAPI.ts +++ b/src/api/CustomerAPI.ts @@ -48,10 +48,10 @@ async function findById(id: string): Promise { }); } -async function findByCode(code: string): Promise { +async function findByCode(code: string, storeId = getCurrentStoreId()): Promise { return await axios.post(`${BASE_URI}/find/code`, null, { params: { - storeId: getCurrentStoreId(), + storeId, code } }); diff --git a/src/assets/styles/global.less b/src/assets/styles/global.less index 48f3d0b..2a4fb07 100644 --- a/src/assets/styles/global.less +++ b/src/assets/styles/global.less @@ -92,6 +92,23 @@ html.theme-dark body { gap: 1rem; } +.t-dropdown-menu { + + &:after { + display: none; + } + + :deep(.t-dropdown-menu__item) { + min-width: 0; + } + + :deep(.t-dropdown-menu__title) { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } +} + .center-tips { color: var(--tui-gray); padding: .75rem; diff --git a/src/components/CustomerSelector.vue b/src/components/CustomerSelector.vue index f3da283..a86ef77 100644 --- a/src/components/CustomerSelector.vue +++ b/src/components/CustomerSelector.vue @@ -9,12 +9,34 @@ >
- + + + + + +
+
+
+
@@ -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(), { + 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([]); const selectedCustomerCache = new Map(); 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 { + 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(() => {