From 81af61573822dae3117b2be924e8f9266a3ff392 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B5=85=E5=BF=B5?= Date: Fri, 4 Sep 2026 14:48:45 +0800 Subject: [PATCH] =?UTF-8?q?docs(plan):=20=E5=A4=8D=E6=A0=B8=E6=AE=8B?= =?UTF-8?q?=E7=95=99=E4=BF=AE=E8=AE=A2=E2=80=94=E2=80=94exportListToCsv?= =?UTF-8?q?=E7=9C=9F=E5=AE=9E=E7=AD=BE=E5=90=8D=E8=90=BD=E5=9C=B0=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E5=9D=97/=E8=B0=83=E5=BA=A6=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E6=94=B9=E7=9B=B8=E5=AF=B9=E6=97=A5=E6=9C=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Code --- .../2026-09-04-yunke-analytics-module.md | 43 ++++++++++++------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/docs/superpowers/plans/2026-09-04-yunke-analytics-module.md b/docs/superpowers/plans/2026-09-04-yunke-analytics-module.md index 98193d9..2bb899a 100644 --- a/docs/superpowers/plans/2026-09-04-yunke-analytics-module.md +++ b/docs/superpowers/plans/2026-09-04-yunke-analytics-module.md @@ -2625,10 +2625,17 @@ async def sched_engine(): async def test_analytics_tick_runs(sched_engine): + # _analytics_tick 用真实「今天」算滚动窗——seed 的 day 必须相对当日, + # 固定 20260904 会在三天后过期(评审残留#2 修正) + import datetime as dt + + from app.storage.summary import day_key + engine, maker = sched_engine + today = day_key(int(dt.datetime.now().timestamp() * 1000)) async with maker() as s: s.add(MsgDailyStats(channel="wechat", owner_account_id="wx1", - peer_kind="friend", peer_id="t1", day=20260904, + peer_kind="friend", peer_id="t1", day=today, send_cnt=1, recv_cnt=0, last_ts=1756950000000, last_is_send=True)) s.add(SensitiveWord(word="私单")) @@ -3061,7 +3068,7 @@ onMounted(load) import { onMounted, ref } from 'vue' import { useRouter } from 'vue-router' import { getChurn, type Channel, type ChurnRow } from '@/api/analytics' -import { exportRowsCsv } from '@/utils/csvExport' // 路径以项目实际为准(grep csvExport) +import { exportListToCsv } from '@/utils/csvExport' const router = useRouter() const channel = ref('wechat') @@ -3105,20 +3112,26 @@ function goChat(row: ChurnRow) { async function exportCsv() { exporting.value = true try { - const all: ChurnRow[] = [] - for (let p = 1; ; p++) { - const out = await getChurn({ + await exportListToCsv({ + fetchPage: (page, pageSize) => getChurn({ channel: channel.value, type: kind.value, tier: tier.value, - min_msgs: 5, page: p, page_size: 100, - }) - all.push(...out.items) - if (all.length >= out.total || out.items.length === 0) break - } - exportRowsCsv(all.map((r) => ({ - 客户: r.nickname, 渠道: r.channel, 归属员工: r.owner_account_id ?? '', - 最后沟通: r.last_chat_time ? new Date(r.last_chat_time).toLocaleString('zh-CN', { hour12: false }) : '', - 历史消息量: r.hist_msgs, 活跃天数: r.active_days, - })), `流失预警_${kind.value}_${channel.value}.csv`) + min_msgs: 5, page, page_size: pageSize, + }), + columns: [ + { key: 'nickname', label: '客户' }, + { key: 'channel', label: '渠道' }, + { key: 'owner_account_id', label: '归属员工' }, + { + key: 'last_chat_time', label: '最后沟通', + format: (r) => (r.last_chat_time + ? new Date(r.last_chat_time).toLocaleString('zh-CN', { hour12: false }) + : '从未沟通'), + }, + { key: 'hist_msgs', label: '历史消息量' }, + { key: 'active_days', label: '活跃天数' }, + ], + filename: `流失预警_${kind.value}_${channel.value}`, + }) // 工具自带翻页+BOM+10000 行 cap } finally { exporting.value = false }