scanner in CustomerSelector.vue

This commit is contained in:
Timi
2026-09-09 17:49:37 +08:00
parent 809bfa8520
commit 8e77b114f9
17 changed files with 804 additions and 410 deletions
+38 -79
View File
@@ -1,29 +1,25 @@
<template>
<div v-can="permissionCodes.stat.read" class="customer-data-charts-page">
<div class="header bg-white">
<div class="buttons">
<t-button class="selector" size="small" theme="primary" variant="outline" @click="selectorVisible = true">
<template #icon>
<t-icon name="user-search" />
</template>
<span>选择客户</span>
</t-button>
<t-button size="small" theme="primary" @click="toggleScanner">
<template #icon>
<t-icon name="scan" />
</template>
<span v-text="scannerVisible ? '关闭扫码' : '扫码'" />
</t-button>
</div>
<div v-if="scannerVisible" class="line scanner-wrapper">
<code-scanner
visible
:status-text="scanStatus"
active-text="正在识别客户编码,请将二维码移入取景框"
@decode="handleDecodedText"
@status-change="handleScanStatusChange"
/>
</div>
<t-input
v-model.trim="customerCode"
class="customer-search"
borderless
clearable
placeholder="输入客户编码或姓名"
@clear="applyCustomerSearch"
@enter="applyCustomerSearch"
>
<template #suffix-icon>
<t-button
size="extra-small"
theme="primary"
variant="outline"
content="选择客户"
@click.stop="selectorVisible = true"
/>
</template>
</t-input>
</div>
<customer-profile-card v-entrance-transition class="customer" :customer="selectedCustomer" />
<div class="charts">
@@ -78,7 +74,6 @@
<script setup lang="ts">
import { Time } from "timi-web";
import CustomerAPI from "@/api/CustomerAPI";
import CodeScanner from "@/components/CodeScanner.vue";
import CustomerSelector from "@/components/CustomerSelector.vue";
import type { EventRecordCalendarListPayload } from "@/components/EventRecordCalendar.vue";
import CustomerChartSummary from "@/pages/customer/component/CustomerChartSummary.vue";
@@ -86,7 +81,6 @@ import CustomerEventRecordCalendarSection from "@/pages/customer/component/Custo
import CustomerEventStructureChart from "@/pages/customer/component/CustomerEventStructureChart.vue";
import CustomerOrderCharts from "@/pages/customer/component/CustomerOrderCharts.vue";
import CustomerRecordTrendChart from "@/pages/customer/component/CustomerRecordTrendChart.vue";
import { useSettingStore } from "@/store/settingStore";
import type { Customer } from "@/types/Customer";
import { permissionCodes } from "@/utils/permission";
@@ -96,7 +90,6 @@ defineOptions({
const route = useRoute();
const router = useRouter();
const settingStore = useSettingStore();
// ---------- 路由 ----------
@@ -105,10 +98,9 @@ const routeCustomerCode = computed(() => String(route.query.customerCode || ""))
const customerCode = ref(routeCustomerCode.value);
const selectedCustomerId = ref(routeCustomerId.value);
watch(routeCustomerCode, (value) => {
if (value) {
customerCode.value = value;
}
watch(routeCustomerCode, async (value) => {
customerCode.value = value;
await loadCustomer(value);
});
watch(routeCustomerId, async (value) => {
@@ -131,18 +123,14 @@ const selectorVisible = ref(false);
const selectedCustomer = ref<Customer>();
const selectedEventIdList = ref<string[]>([]);
watch(customerCode, async (value) => {
await loadCustomer(value);
});
async function loadCustomer(code: string): Promise<void> {
const normalizedCode = code.trim();
if (!normalizedCode) {
const keyword = code.trim();
if (!keyword) {
clearLoadedCustomer();
return;
}
const customer = await CustomerAPI.findByCode(normalizedCode);
const customer = await CustomerAPI.findByCode(keyword) || await CustomerAPI.findByName(keyword);
if (!customer) {
clearLoadedCustomer();
return;
@@ -152,6 +140,10 @@ async function loadCustomer(code: string): Promise<void> {
selectedCustomerId.value = customer.id || "";
}
async function applyCustomerSearch(): Promise<void> {
await loadCustomer(customerCode.value);
}
async function loadCustomerById(id: string): Promise<void> {
const normalizedId = id.trim();
if (!normalizedId) {
@@ -162,6 +154,7 @@ async function loadCustomerById(id: string): Promise<void> {
try {
selectedCustomer.value = await CustomerAPI.detail(normalizedId);
selectedCustomerId.value = selectedCustomer.value.id || "";
customerCode.value = selectedCustomer.value.code || selectedCustomer.value.name || "";
} catch (err) {
clearLoadedCustomer();
console.warn("客户详情加载失败", err);
@@ -206,36 +199,6 @@ function getCurrentRouteDepth(): number {
return Number(route.meta.depth ?? 0);
}
// ---------- 扫码 ----------
const scannerVisible = ref(false);
const scanStatus = ref("点击扫码开始识别");
let latestDecodedText = "";
async function toggleScanner(): Promise<void> {
scannerVisible.value = !scannerVisible.value;
if (scannerVisible.value) {
latestDecodedText = "";
scanStatus.value = "正在启动扫码预览";
}
}
async function handleDecodedText(rawText: string): Promise<void> {
const normalizedText = rawText.trim();
if (!normalizedText || normalizedText === latestDecodedText) {
return;
}
latestDecodedText = normalizedText;
await settingStore.playScanSuccessSound();
customerCode.value = normalizedText;
scanStatus.value = `已识别 ${normalizedText},正在加载客户档案`;
scannerVisible.value = false;
}
function handleScanStatusChange(value: string): void {
scanStatus.value = value;
}
// ---------- 图表日期 ----------
const monthCursor = ref(getMonthStart(new Date()));
@@ -300,18 +263,14 @@ function getDateEnd(date: Date): Date {
position: sticky;
box-shadow: var(--td-shadow-1);
.buttons {
gap: 1rem;
display: flex;
.customer-search {
--td-input-vertical-padding: .25rem 1rem;
.selector {
flex: 1;
min-width: 0;
}
}
.scanner-wrapper {
margin-top: 1rem;
width: 100%;
border: 1px solid var(--td-component-border);
background: var(--td-bg-color-container);
padding-right: .3rem;
border-radius: 1rem;
}
}
@@ -329,7 +288,7 @@ function getDateEnd(date: Date): Date {
:global(html.theme-dark) {
.customer-data-charts-page {
.selector {
.customer-search {
background: #10161f;
}
}