From f51623d6526df02c378e8b1e81a3b327fac372a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B5=85=E5=BF=B5?= Date: Fri, 7 Aug 2026 18:52:07 +0800 Subject: [PATCH] =?UTF-8?q?feat(staff):=20=E5=BE=AE=E4=BF=A1=205=20?= =?UTF-8?q?=E6=95=B0=E5=AD=97=20modal=20=E6=8D=A2=E6=9C=AC=E5=9C=B0?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E6=BA=90=EF=BC=88metric=20=E8=BF=87=E6=BB=A4?= =?UTF-8?q?=20+=20=E5=90=88=E6=88=90=E5=88=86=E9=A1=B5=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude --- yunkes/src/views/StaffList.vue | 45 +++++++++++++++++++++++++++++++--- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/yunkes/src/views/StaffList.vue b/yunkes/src/views/StaffList.vue index c224268..b2a2fb2 100644 --- a/yunkes/src/views/StaffList.vue +++ b/yunkes/src/views/StaffList.vue @@ -3,7 +3,7 @@ import { computed, onMounted, reactive, ref } from 'vue' import { useRouter } from 'vue-router' import { ElMessage } from 'element-plus' import { getAccounts, getCustomers, getMessages, getStaffChatStats } from '@/api' -import type { Account, Channel, StaffChatStats } from '@/api/types' +import type { Account, Channel, Page, StaffChatCustomer, StaffChatStats } from '@/api/types' import YkStaffDetailModal from '@/components/YkStaffDetailModal.vue' import { useChannelStore } from '@/stores/channel' import { channelLabel, formatTime } from '@/utils/format' @@ -347,11 +347,50 @@ function commModalColsFor(metric: ChatMetric): any[] { } return cols } +/** 按 metric 过滤客户明细(口径见 spec §4;last_is_send 为 null 时既非最后回复也非未回复)。 */ +function metricFilter(c: StaffChatCustomer, m: ChatMetric): boolean { + switch (m) { + case 'chat': + return true + case 'effective': + return c.send_count >= 1 && c.receive_count >= 1 + case 'follow': + return c.interaction_days >= 2 + case 'last_reply': + return c.last_is_send === 1 + case 'no_reply': + return c.last_is_send === 0 + } +} + +/** 微信员工 5 数字 → 明细 modal(数据源:本地 staff-chat-stats,按 metric 过滤 + 合成分页)。 */ function openWechatCommModal(row: Account, metric: ChatMetric): void { - // Task 3 接真实数据 + metric 过滤 + 合成分页。此处先立标题与列骨架。 detailTitle.value = `${METRIC_TITLES[metric]} · ${row.user_name ?? row.account_id}` detailCols.value = commModalColsFor(metric) - detailFetcher.value = async () => ({ items: [], total: 0, page: 1, page_size: 10 }) + detailFetcher.value = async (page: number, page_size: number): Promise> => { + // 复用已缓存的 stats;点击早于列表加载完成则按需补拉。 + let stats = staffStats.value[row.account_id] + if (!stats) { + try { + stats = await getStaffChatStats({ + owner_account_id: row.account_id, + start: statsStart.value, + end: statsEnd.value, + }) + staffStats.value[row.account_id] = stats + } catch { + return { items: [], total: 0, page, page_size } + } + } + const filtered = stats.customers.filter((c) => metricFilter(c, metric)) + const startIdx = (page - 1) * page_size + return { + items: filtered.slice(startIdx, startIdx + page_size), + total: filtered.length, + page, + page_size, + } + } detailOpen.value = true }