From 9e4a5ee536c45bee66a8e8a24f1f6c4d1c05a786 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B5=85=E5=BF=B5?= Date: Wed, 12 Aug 2026 14:09:43 +0800 Subject: [PATCH] =?UTF-8?q?fix(ui):=20StaffDetail=E5=88=86=E9=A1=B5?= =?UTF-8?q?=E6=9F=A5=E6=89=BE+SmsList=E6=97=A5=E6=9C=9F=E7=AD=9B=E9=80=89+?= =?UTF-8?q?SyncStatus=E8=87=AA=E5=8A=A8=E5=88=B7=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude --- yunkes/src/views/SmsList.vue | 22 ++++++++++++++++ yunkes/src/views/StaffDetail.vue | 40 ++++++++++++++++++++--------- yunkes/src/views/SyncStatus.vue | 43 +++++++++++++++++++++++++++++--- 3 files changed, 90 insertions(+), 15 deletions(-) diff --git a/yunkes/src/views/SmsList.vue b/yunkes/src/views/SmsList.vue index 8de8c1d..058064b 100644 --- a/yunkes/src/views/SmsList.vue +++ b/yunkes/src/views/SmsList.vue @@ -13,6 +13,9 @@ const pageSize = ref(20) const loading = ref(false) const errorMsg = ref(null) +/** [start, end] as Date[] | null. null = no date filter (all SMS). */ +const dateRange = ref<[Date, Date] | null>(null) + // All-fields drawer state. const drawerOpen = ref(false) const drawerSms = ref(null) @@ -24,6 +27,8 @@ async function load(): Promise { const res = await getSms({ page: page.value, page_size: pageSize.value, + start: dateRange.value?.[0]?.getTime(), + end: dateRange.value?.[1]?.getTime(), }) list.value = res.items total.value = res.total @@ -46,6 +51,12 @@ function handlePageSizeChange(s: number): void { load() } +/** New date range → reset to page 1 and reload. */ +function onDateChange(): void { + page.value = 1 + load() +} + /** * Read a raw_json camelCase key off an SMS, preferring the structured * snake_case column when it is populated. The first argument is the row so the @@ -96,6 +107,17 @@ onMounted(load)

短信记录

共 {{ total }} 条 + { loading.value = true notFound.value = false try { - // getAccounts drops the channel param when undefined → union of both tabs. - // page_size=999 is plenty; the staff-account dataset is small (~50 rows). - const res = await getAccounts({ page: 1, page_size: 999 }) const id = props.userId - const match = - res.items.find((a) => a.user_id === id) ?? - res.items.find((a) => a.account_id === id) ?? - null + const match = await findAccountById(id) if (!match) { notFound.value = true } else { @@ -123,6 +116,29 @@ async function load(): Promise { } } +/** + * Walk both channel tables page by page until we hit the requested id. Stops + * early on a match so the common case is a single round-trip; falls through + * to the next channel on exhaustion. Returns null if neither channel has it. + */ +async function findAccountById(id: string): Promise { + const pageSize = 100 + for (const ch of ['wechat', 'whatsapp'] as const) { + let page = 1 + while (true) { + const res = await getAccounts({ channel: ch, page, page_size: pageSize }) + const found = + res.items.find((a: Account) => a.user_id === id) ?? + res.items.find((a: Account) => a.account_id === id) ?? + null + if (found) return found + if (res.items.length < pageSize) break // last page + page++ + } + } + return null +} + function goBack(): void { // router.back() lands on the originating list if it exists; fall back to // the staff list for direct URL entry / refresh on the detail page. diff --git a/yunkes/src/views/SyncStatus.vue b/yunkes/src/views/SyncStatus.vue index 390223f..e172a41 100644 --- a/yunkes/src/views/SyncStatus.vue +++ b/yunkes/src/views/SyncStatus.vue @@ -1,5 +1,5 @@